pickle — Сериализация объектов Python
Исходный код: Lib/pickle.py
Модуль pickle реализует двоичные протоколы для сериализации и десериализации структуры Python-объектов. «Закачивание» — это процесс преобразования иерархии объектов Python в поток байтов, а «раскачивание» — обратный процесс, при котором поток байтов (из бинарного файла или объекта типа байты) преобразуется обратно в иерархию объектов. Закачивание (и раскачивание) также известны как «сериализация», «маршаллинг», 1 или «сглаживание»; однако, чтобы избежать путаницы, здесь используются термины «закачивание» и «раскачивание».
Предупреждение
Модуль pickle небезопасен. Размораживайте только данные, которым доверяете.
Возможна конструкция вредоносных данных pickle, которая выполнит произвольный код во время размораживания. Никогда не размораживайте данные, которые могли быть получены из недоверенного источника или которые могли быть изменены.
Рассмотрите подписание данных с помощью hmac, если вам необходимо убедиться, что данные не были изменены.
Более безопасные форматы сериализации, такие как json, могут быть более подходящими, если вы обрабатываете недоверенные данные. См. Сравнение с json.
Связь с другими модулями Python
Сравнение с marshal
В Python есть более примитивный модуль сериализации, называемый marshal, но в общем случае pickle всегда должен быть предпочтительным способом сериализации объектов Python. marshal существует в первую очередь для поддержки файлов .pyc Python.
Модуль pickle отличается от marshal по нескольким важным аспектам:
-
Модуль
pickleотслеживает объекты, которые он уже сериализовал, чтобы последующие ссылки на тот же объект не сериализовались повторно.marshalэтого не делает.Это имеет последствия как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты — это объекты, содержащие ссылки на сами себя. marshal не обрабатывает их, и попытка маршаллировать рекурсивные объекты приведет к аварии интерпретатора Python. Обмен объектами происходит, когда несколько ссылок на один и тот же объект находятся в разных местах иерархии сериализуемых объектов.
pickleхранит такие объекты только один раз и гарантирует, что все другие ссылки указывают на копию основного объекта. Обмен объектами сохраняется, что очень важно для изменяемых объектов. -
marshalнельзя использовать для сериализации пользовательских классов и их экземпляров.pickleможет сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при хранении объекта. - Формат сериализации
marshalне гарантируется, что будет переносимым между версиями Python. Поскольку его основная задача — поддержка файлов.pyc, разработчики Python оставляют за собой право изменять формат сериализации несовместимым образом в случае необходимости. Формат сериализацииpickleгарантированно совместим между версиями Python при выборе совместимого протокола pickle и коде pickling и unpickling, который обрабатывает различия между типами Python 2 и Python 3, если ваши данные пересекают эту уникальную границу разрыва совместимости языка.
Сравнение с json
Существуют фундаментальные различия между протоколами pickle и JSON (JavaScript Object Notation):
- JSON — это текстовый формат сериализации (он выводит текст unicode, хотя большинство времени он кодируется в
utf-8), в то время как pickle — это двоичный формат сериализации; - JSON легко читаем человеком, а pickle — нет;
- JSON является взаимосовместимым и широко используется вне экосистемы Python, а pickle — специфичен для Python;
- JSON по умолчанию может представлять только подмножество встроенных типов Python, и никаких пользовательских классов; pickle может представлять очень большое количество типов Python (многие из них автоматически, благодаря умному использованию средств интроспекции Python; сложные случаи могут быть решены путем реализации специфических API объектов);
- В отличие от pickle, десериализация недоверенного JSON сама по себе не создает уязвимости произвольного выполнения кода.
См. также
Модуль json: модуль стандартной библиотеки, позволяющий сериализацию и десериализацию JSON.
Формат потока данных
Формат данных, используемый модулем pickle, специфичен для Python. Это имеет преимущество отсутствия ограничений, накладываемых внешними стандартами, такими как JSON или XDR (которые не могут представить совместное использование указателей); однако это означает, что программы, не написанные на Python, могут не быть способны восстановить сериализованные Python-объекты.
По умолчанию, модуль pickle использует относительно компактное двоичное представление. Если вам необходимы оптимальные характеристики размера, вы можете эффективно сжать данные, сериализованные с помощью pickle.
Модуль pickletools содержит инструменты для анализа потоков данных, генерируемых модулем pickle. Исходный код модуля pickletools содержит подробные комментарии по поводу кодов операций, используемых протоколами pickle.
В настоящее время существует 6 различных протоколов, которые могут быть использованы для сериализации. Чем выше используемый протокол, тем более свежая версия Python необходима для чтения созданного файла pickle.
- Протокол версии 0 — это исходный «человекочитаемый» протокол и обратно совместим с более ранними версиями Python.
- Протокол версии 1 — это старый двоичный формат, который также совместим с более ранними версиями Python.
- Протокол версии 2 был введён в Python 2.3. Он обеспечивает гораздо более эффективную сериализацию классов нового стиля. Для получения информации об улучшениях, внесённых протоколом 2, обратитесь к PEP 307.
- Протокол версии 3 был добавлен в Python 3.0. Он имеет явную поддержку объектов
bytesи не может быть десериализован Python 2.x. Это был протокол по умолчанию в Python 3.0–3.7. - Протокол версии 4 был добавлен в Python 3.4. Он добавляет поддержку очень больших объектов, сериализацию большего количества типов объектов и некоторые оптимизации формата данных. Это протокол по умолчанию начиная с Python 3.8. Для получения информации об улучшениях, внесённых протоколом 4, обратитесь к PEP 3154.
- Протокол версии 5 был добавлен в Python 3.8. Он добавляет поддержку данных вне полосы пропускания и ускорение для данных в полосе пропускания. Для получения информации об улучшениях, внесённых протоколом 5, обратитесь к PEP 574.
Примечание
Сериализация — это более примитивное понятие, чем персистентность; хотя модуль pickle читает и записывает файлы, он не решает проблему именования постоянных объектов, ни (ещё более сложную) проблему одновременного доступа к постоянным объектам. Модуль pickle может преобразовать сложный объект в поток байтов, и он может преобразовать поток байтов в объект с той же внутренней структурой. Наиболее очевидным действием с этими потоками байтов является запись их в файл, но также можно представить их отправку по сети или хранение в базе данных. Модуль shelve предоставляет простой интерфейс для сериализации и десериализации объектов в файлах базы данных в стиле DBM.
Интерфейс модуля
Для сериализации иерархии объектов просто вызовите функцию dumps(). Аналогично, для десериализации потока данных вызовите функцию loads(). Однако, если вы хотите иметь больший контроль над сериализацией и десериализацией, вы можете создать объект Pickler или Unpickler соответственно.
Модуль pickle предоставляет следующие константы:
-
pickle.HIGHEST_PROTOCOL -
Целое число, наивысшая версия протокола, доступная. Это значение может быть передано в качестве значения protocol функциям
dump()иdumps(), а также конструкторуPickler.
-
pickle.DEFAULT_PROTOCOL -
Целое число, значение по умолчанию версии протокола, используемой для сериализации. Может быть меньше, чем
HIGHEST_PROTOCOL. В настоящее время протокол по умолчанию — 4, впервые представленный в Python 3.4 и несовместимый с предыдущими версиями.Изменено в версии 3.0: Протокол по умолчанию — 3.
Изменено в версии 3.8: Протокол по умолчанию — 4.
Модуль pickle предоставляет следующие функции, чтобы сделать процесс сериализации удобнее:
-
pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None) -
Записать сериализованное представление объекта obj в открытый объект файла file. Это эквивалентно
Pickler(file, protocol).dump(obj).Аргументы file, protocol, fix_imports и buffer_callback имеют то же значение, что и в конструкторе
Pickler.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
-
pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None) -
Вернуть сериализованное представление объекта obj как объект
bytes, вместо записи его в файл.Аргументы protocol, fix_imports и buffer_callback имеют то же значение, что и в конструкторе
Pickler.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
-
pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Прочитать сериализованное представление объекта из открытого объекта файла file и вернуть воссозданную иерархию объектов, указанную в нём. Это эквивалентно
Unpickler(file).load().Версия протокола сериализации определяется автоматически, поэтому аргумент protocol не нужен. Байты после сериализованного представления объекта игнорируются.
Аргументы file, fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе
Unpickler.Изменено в версии 3.8: Добавлен аргумент buffers.
-
pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Вернуть воссозданную иерархию объектов сериализованного представления data объекта. data должен быть объектом типа bytes-like object.
Версия протокола сериализации определяется автоматически, поэтому аргумент protocol не нужен. Байты после сериализованного представления объекта игнорируются.
Аргументы fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе
Unpickler.Изменено в версии 3.8: Добавлен аргумент buffers.
Модуль pickle определяет три исключения:
-
exception pickle.PickleError -
Базовый класс для других исключений сериализации. Наследуется от
Exception.
-
exception pickle.PicklingError -
Исключение, генерируемое, когда встречается несериализуемый объект в
Pickler. Наследуется отPickleError.Обратитесь к Что можно сериализовать и десериализовать?, чтобы узнать, какие типы объектов можно сериализовать.
-
exception pickle.UnpicklingError -
Исключение, генерируемое, когда возникает проблема при десериализации объекта, например, повреждение данных или нарушение безопасности. Наследуется от
PickleError.Обратите внимание, что при десериализации могут быть вызваны и другие исключения, включая (но не ограничиваясь ими) AttributeError, EOFError, ImportError и IndexError.
Модуль pickle экспортирует три класса: Pickler, Unpickler и PickleBuffer.
-
class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None) -
Это принимает бинарный файл для записи потока данных pickle.
Необязательный аргумент protocol, целое число, сообщает сериализатору использовать указанный протокол; поддерживаемые протоколы — от 0 до
HIGHEST_PROTOCOL. Если не указано, по умолчанию используетсяDEFAULT_PROTOCOL. Если указано отрицательное число, выбираетсяHIGHEST_PROTOCOL.Аргумент file должен иметь метод write(), принимающий один аргумент типа bytes. Таким образом, это может быть файл на диске, открытый для двоичного записи, экземпляр
io.BytesIOили любой другой пользовательский объект, удовлетворяющий этому интерфейсу.Если fix_imports имеет значение true, а protocol меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных pickle был читаем с помощью Python 2.
Если buffer_callback равно None (по умолчанию), представления буферов сериализуются в file как часть потока pickle.
Если buffer_callback не равно None, то его можно вызывать любое количество раз с представлением буфера. Если обратный вызов возвращает ложное значение (например, None), заданный буфер вне потока; в противном случае буфер сериализуется внутри потока, т.е. внутри потока pickle.
Ошибка возникает, если buffer_callback не равно None, а protocol равно None или меньше 5.
Изменено в версии 3.8: Аргумент buffer_callback был добавлен.
-
dump(obj) -
Записать сериализованное представление obj в открытый объект файла, указанный в конструкторе.
-
persistent_id(obj) -
По умолчанию ничего не делает. Это существует, чтобы подкласс мог переопределить его.
Если
persistent_id()возвращаетNone, obj сериализуется в обычном режиме. Любое другое значение приводит к тому, чтоPicklerгенерирует возвращённое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно быть определеноUnpickler.persistent_load(). Обратите внимание, что значение, возвращаемоеpersistent_id(), само по себе не может иметь постоянный идентификатор.Подробные сведения и примеры использования см. в разделе Сохранение внешних объектов.
-
dispatch_table -
Таблица переходов объекта сериализатора — это реестр функций редукции, которые можно объявить с помощью
copyreg.pickle(). Это отображение, ключи которого — классы, а значения — функции редукции. Функция редукции принимает один аргумент соответствующего класса и должна соответствовать тому же интерфейсу, что и метод__reduce__().По умолчанию у объекта сериализатора не будет атрибута
dispatch_table, и он вместо этого будет использовать глобальную таблицу переходов, управляемую модулемcopyreg. Однако для настройки сериализации для конкретного объекта сериализатора можно установить атрибутdispatch_tableна объект типа dict. Кроме того, если подклассPicklerимеет атрибутdispatch_table, то он будет использоваться в качестве таблицы переходов по умолчанию для экземпляров этого класса.Примеры использования см. в разделе Таблицы переходов.
Новое в версии 3.3.
-
reducer_override(obj) -
Специальный редуктор, который может быть определен в подклассах
Pickler. Этот метод имеет приоритет над любым редуктором вdispatch_table. Он должен соответствовать тому же интерфейсу, что и метод__reduce__(), и может необязательно возвращатьNotImplemented, чтобы перейти к редукторам, зарегистрированным вdispatch_table, для сериализацииobj.Подробный пример см. в разделе Настройка редукции для типов, функций и других объектов.
Новое в версии 3.8.
-
fast -
Устарело. Включить быстрый режим, если значение равно истинному. Быстрый режим отключает использование memo, тем самым ускоряя процесс сериализации, не генерируя избыточные opcodes PUT. Его нельзя использовать с самоссылочными объектами, в противном случае
Picklerбудет рекурсировать бесконечно.Используйте
pickletools.optimize(), если вам нужны более компактные pickles.
-
-
class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Это принимает двоичный файл для чтения потока данных pickle.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется.
Аргумент file должен иметь три метода: метод read() с целочисленным аргументом, метод readinto() с аргументом буфера и метод readline() без аргументов, как в интерфейсе
io.BufferedIOBase. Таким образом, file может быть файлом на диске, открытым для двоичного чтения, объектомio.BytesIOили любым другим пользовательским объектом, соответствующим этому интерфейсу.Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости для потока pickle, сгенерированного Python 2. Если fix_imports истинно, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. encoding и errors сообщают pickle, как декодировать 8-битные строковые экземпляры, закодированные Python 2; они по умолчанию равны ‘ASCII’ и ‘strict’ соответственно. encoding может быть ‘bytes’, чтобы читать эти 8-битные строковые экземпляры как объекты bytes. Используя
encoding='latin1'требуется для распаковки массивов NumPy и экземпляровdatetime,dateиtime, закодированных Python 2.Если buffers равно None (по умолчанию), то все данные, необходимые для десериализации, должны содержаться в потоке pickle. Это означает, что аргумент buffer_callback был None при создании
Pickler(или при вызовеdump()илиdumps()).Если buffers не равно None, оно должно быть итерируемым объектом с поддержкой буферов, который используется каждый раз, когда поток pickle ссылается на внепотоковый просмотр буфера. Такие буферы были переданы в buffer_callback объекта Pickler.
Изменено в версии 3.8: Добавлен аргумент buffers.
-
load() -
Прочитать закодированное представление объекта из открытого объекта файла, заданного в конструкторе, и вернуть реконструированную иерархию объектов, указанную в нем. Байты после закодированного представления объекта игнорируются.
-
persistent_load(pid) -
По умолчанию возбудить
UnpicklingError.Если определено,
persistent_load()должно вернуть объект, указанный постоянным идентификатором pid. Если встречен неверный постоянный идентификатор, должно быть возбужденоUnpicklingError.См. Сохранение внешних объектов для получения подробной информации и примеров использования.
-
find_class(module, name) -
При необходимости импортировать module и вернуть объект, названный name из него, где аргументы module и name — объекты
str. Обратите внимание, в отличие от названия,find_class()также используется для поиска функций.Подклассы могут переопределить это, чтобы получить контроль над тем, какие типы объектов и как они могут загружаться, потенциально уменьшая риски безопасности. Обратитесь к Ограничение глобальных переменных для получения подробностей.
Возбуждает событие аудита
pickle.find_classс аргументамиmodule,name.
-
-
class pickle.PickleBuffer(buffer) -
Обёртка для буфера, представляющего сохраняемые данные. buffer должен быть объектом, предоставляющим буфер, например, объект подобный строке байтов или многомерный массив.
PickleBufferсам по себе является поставщиком буфера, поэтому его можно передать другим API, ожидающим объект, предоставляющий буфер, например,memoryview.Объекты
PickleBufferмогут быть сериализованы только с помощью протокола pickle 5 или выше. Они подходят для внепотоковой сериализации.Новое в версии 3.8.
-
raw() -
Возвращает
memoryviewобласти памяти, лежащей в основе этого буфера. Возвращаемый объект является одномерным, непрерывным в C видением памяти с форматомB(беззнаковые байты).BufferErrorвозбуждается, если буфер не является ни C-, ни Fortran-непрерывным.
-
release() -
Освободить буфер, предоставленный объектом PickleBuffer.
-
Что можно закодировать и раскодировать?
Следующие типы могут быть закодированы:
- встроенные константы (
None,True,False,Ellipsis, иNotImplemented); - целые, вещественные, комплексные числа;
- строки, байты, bytearrays;
- кортежи, списки, множества и словари, содержащие только закодируемые объекты;
- функции (встроенные и пользовательские) доступные из верхнего уровня модуля (с использованием
def, а неlambda); - классы, доступные из верхнего уровня модуля;
- экземпляры таких классов, результат вызова
__getstate__()которого закодируем (см. раздел Запись экземпляров классов для получения подробной информации).
Попытки закодировать незакодируемые объекты приведут к возбуждению исключения PicklingError; в этом случае может быть уже записано неопределенное количество байтов в основной файл. Попытка закодировать сильно рекурсивную структуру данных может превысить максимальную глубину рекурсии, в этом случае будет возбуждено RecursionError. Вы можете осторожно увеличить этот предел с помощью sys.setrecursionlimit().
Обратите внимание, что функции (встроенные и пользовательские) закодированы с полным полным именем, а не по значению. 2 Это означает, что закодировано только имя функции, а также имя содержащего модуля и классов. Модуль определения должен быть импортируемым в среде раскодирования, и модуль должен содержать названный объект, в противном случае будет возбуждено исключение. 3
Аналогично, классы закодированы по полному имени, поэтому в среде раскодирования применяются те же ограничения. Обратите внимание, что код и данные класса не закодированы вместе с ними, поэтому в следующем примере атрибут класса attr не восстанавливается в среде раскодирования:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
Эти ограничения объясняют, почему закодируемые функции и классы должны быть определены на верхнем уровне модуля.
Аналогично, когда экземпляры классов закодированы, код и данные их класса не закодированы вместе с ними. Закодированы только данные экземпляра. Это сделано намеренно, чтобы вы могли исправлять ошибки в классе или добавлять методы к классу и по-прежнему загружать объекты, созданные с более ранней версией класса. Если вы планируете иметь долгоживущие объекты, которые будут видеть много версий класса, может быть полезно поместить номер версии в объекты, чтобы подходящие преобразования могли быть выполнены методом класса __setstate__().
Сериализация экземпляров классов
В этом разделе мы описываем общие механизмы, доступные для определения, настройки и управления тем, как сериализуются и десериализуются экземпляры классов.
В большинстве случаев для сериализуемости экземпляров дополнительного кода не требуется. По умолчанию pickle будет получать класс и атрибуты экземпляра через интроспекцию. При десериализации экземпляра класса его метод __init__() обычно не вызывается. По умолчанию сначала создается неинициализированный экземпляр, а затем восстанавливаются сохранённые атрибуты. Следующий код демонстрирует реализацию этого поведения:
def save(obj):
return (obj.__class__, obj.__dict__)
def restore(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
Классы могут изменить поведение по умолчанию, предоставив один или несколько специальных методов:
-
object.__getnewargs_ex__() -
В протоколах 2 и выше классы, реализующие метод
__getnewargs_ex__(), могут диктовать значения, передаваемые методу__new__()при десериализации. Метод должен возвращать пару(args, kwargs), где args — кортеж позиционных аргументов, а kwargs — словарь именованных аргументов для построения объекта. Они будут переданы методу__new__()при десериализации.Вы должны реализовать этот метод, если методу
__new__()вашего класса требуются только именованные аргументы. В противном случае для совместимости рекомендуется реализовать__getnewargs__().Изменено в версии 3.6:
__getnewargs_ex__()теперь используется в протоколах 2 и 3.
-
object.__getnewargs__() -
Этот метод выполняет аналогичную задачу, что и
__getnewargs_ex__(), но поддерживает только позиционные аргументы. Он должен возвращать кортеж аргументовargs, которые будут переданы методу__new__()при десериализации.__getnewargs__()не будет вызван, если определен__getnewargs_ex__().Изменено в версии 3.6: До Python 3.6
__getnewargs__()вызывался вместо__getnewargs_ex__()в протоколах 2 и 3.
-
object.__getstate__() -
Классы могут дополнительно влиять на то, как сериализуются их экземпляры, переопределив метод
__getstate__(). Он вызывается, а возвращаемый объект сериализуется как содержимое экземпляра вместо состояния по умолчанию. Существует несколько случаев:- Для класса без атрибута
__dict__и без__slots__, состояние по умолчанию —None. - Для класса с атрибутом
__dict__и без__slots__, состояние по умолчанию —self.__dict__. - Для класса с атрибутом
__dict__и__slots__, состояние по умолчанию — кортеж, состоящий из двух словарей:self.__dict__, и словарь, сопоставляющий имена слотов значениям слотов. В последнем включаются только слоты, имеющие значение. - Для класса с
__slots__и без атрибута__dict__, состояние по умолчанию — кортеж, первый элемент которого —None, а второй — словарь, сопоставляющий имена слотов значениям слотов, описанным в предыдущем пункте.
Изменено в версии 3.11: Добавлена реализация по умолчанию метода
__getstate__()в классеobject. - Для класса без атрибута
-
object.__setstate__(state) -
При десериализации, если класс определяет
__setstate__(), он вызывается со состоянием, десериализованным из данных. В этом случае нет требования, чтобы объект состояния был словарем. В противном случае сериализованное состояние должно быть словарем, а его элементы присваиваются словарю нового экземпляра.Примечание
Если
__getstate__()возвращает ложное значение, метод__setstate__()не будет вызван при десериализации.
Дополнительную информацию о том, как использовать методы __getstate__() и __setstate__(), см. в разделе Обработка объектов с состоянием.
Примечание
При десериализации некоторые методы, такие как __getattr__(), __getattribute__(), или __setattr__(), могут быть вызваны для экземпляра. В случае, если эти методы полагаются на то, что некоторое внутреннее инвариантное свойство истинно, тип должен реализовать __new__(), чтобы установить такой инвариант, так как __init__() не вызывается при десериализации экземпляра.
Как мы увидим, pickle не использует напрямую описанные выше методы. Фактически, эти методы являются частью протокола копирования, который реализует специальный метод __reduce__(). Протокол копирования предоставляет унифицированный интерфейс для получения данных, необходимых для сериализации и копирования объектов. 4
Несмотря на свою мощь, реализация __reduce__() напрямую в ваших классах может привести к ошибкам. По этой причине разработчики классов должны использовать высокоуровневый интерфейс (т.е., __getnewargs_ex__(), __setstate__()) всякий раз, когда это возможно. Однако мы покажем случаи, когда использование __reduce__() является единственным вариантом или приводит к более эффективной сериализации, или к обоим.
-
object.__reduce__() -
Интерфейс в настоящее время определен следующим образом. Метод
__reduce__()не принимает аргументов и должен возвращать либо строку, либо, предпочтительно, кортеж (возвращаемый объект часто называют «значением сокращения»).Если возвращается строка, строка должна интерпретироваться как имя глобальной переменной. Это должно быть локальное имя объекта относительно его модуля; модуль pickle ищет пространство имен модуля, чтобы определить модуль объекта. Это поведение обычно полезно для синглтонов.
Когда возвращается кортеж, его длина должна быть от двух до шести элементов. Дополнительные элементы могут быть опущены или
Noneможет быть предоставлен в качестве их значения. Семантика каждого элемента приведена в порядке:- Вызываемый объект, который будет вызван для создания начальной версии объекта.
- Кортеж аргументов для вызываемого объекта. Пустой кортеж должен быть предоставлен, если вызываемый объект не принимает никаких аргументов.
- Необязательно, состояние объекта, которое будет передано методу объекта
__setstate__(), как описано ранее. Если у объекта нет такого метода, значение должно быть словарем, и оно будет добавлено к атрибуту объекта__dict__. - Необязательно, итератор (а не последовательность), возвращающий последовательные элементы. Эти элементы будут добавлены к объекту либо с использованием
obj.append(item)или, по частям, с использованиемobj.extend(list_of_items). Это используется в первую очередь для подклассов списков, но может использоваться и другими классами, если у них есть методыappend()иextend()с соответствующей сигнатурой. (Использованиеappend()илиextend()зависит от версии протокола pickle, а также от количества добавляемых элементов, поэтому оба должны поддерживаться.) - Необязательно, итератор (не последовательность), возвращающий последовательные пары ключ-значение. Эти элементы будут сохранены в объекте с помощью
obj[key] = value. Это используется в первую очередь для подклассов словарей, но может использоваться и другими классами, если они реализуют__setitem__(). -
Необязательно, вызываемый объект с сигнатурой
(obj, state). Этот вызываемый объект позволяет пользователю программно контролировать поведение обновления состояния конкретного объекта вместо использования статического методаobj__setstate__(). Если неNone, этот вызываемый объект будет иметь приоритет над методом__setstate__()объектаobj.New in version 3.8: Дополнительный шестой элемент кортежа,
(obj, state), был добавлен.
-
object.__reduce_ex__(protocol) -
В качестве альтернативы, может быть определен метод
__reduce_ex__(). Единственное различие заключается в том, что этот метод должен принимать один целочисленный аргумент — версию протокола. При определении pickle будет отдавать предпочтение этому методу по сравнению с методом__reduce__(). Кроме того, метод__reduce__()автоматически становится синонимом расширенной версии. Основное использование этого метода заключается в предоставлении обратной совместимости значений сокращения для более старых версий Python.
Сохранение внешних объектов
Для сохранения объектов модуль pickle поддерживает понятие ссылки на объект, находящийся вне потока сериализованных данных. Такие объекты ссылаются на постоянный идентификатор, который должен быть либо строкой из буквенно-цифровых символов (для протокола 0) 5, либо просто произвольным объектом (для любого более нового протокола).
Разрешение таких постоянных идентификаторов не определяется модулем pickle; он делегирует это разрешение методам, определенным пользователем, для сериализатора и десериализатора, persistent_id() и persistent_load() соответственно.
Для сериализации объектов с внешним постоянным идентификатором сериализатор должен иметь пользовательский метод persistent_id(), который принимает объект в качестве аргумента и возвращает либо None , либо постоянный идентификатор для этого объекта. При возвращении None сериализатор просто сериализует объект обычным способом. При возвращении строки постоянного идентификатора сериализатор сериализует этот объект вместе с маркером, чтобы десериализатор мог распознать его как постоянный идентификатор.
Для десериализации внешних объектов десериализатор должен иметь пользовательский метод persistent_load(), который принимает объект постоянного идентификатора и возвращает сохраненный объект.
Ниже приведен исчерпывающий пример, демонстрирующий, как можно использовать постоянные идентификаторы для сериализации внешних объектов по ссылке.
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
Таблицы диспетчеризации
Если требуется настроить сериализацию некоторых классов без изменения кода, зависящего от сериализации, можно создать сериализатор с личной таблицей диспетчеризации.
Глобальная таблица диспетчеризации, управляемая модулем copyreg, доступна как copyreg.dispatch_table. Таким образом, можно использовать измененную копию copyreg.dispatch_table в качестве личной таблицы диспетчеризации.
Например
f = io.BytesIO() p = pickle.Pickler(f) p.dispatch_table = copyreg.dispatch_table.copy() p.dispatch_table[SomeClass] = reduce_SomeClass
создает экземпляр pickle.Pickler с личной таблицей диспетчеризации, которая обрабатывает класс SomeClass специально. В качестве альтернативы, код
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
делает то же самое, но все экземпляры MyPickler по умолчанию будут использовать общую личную таблицу диспетчеризации. С другой стороны, код
copyreg.pickle(SomeClass, reduce_SomeClass) f = io.BytesIO() p = pickle.Pickler(f)
изменяет глобальную таблицу диспетчеризации, используемую всеми пользователями модуля copyreg.
Обработка состояний объектов
Ниже приведен пример, показывающий, как изменить поведение сериализации для класса. Класс TextReader открывает текстовый файл и возвращает номер строки и содержимое строки каждый раз, когда вызывается его метод readline(). Если экземпляр TextReader сериализуется, все атрибуты, кроме члена объекта файла, сохраняются. При десериализации экземпляра файл открывается заново, и чтение возобновляется с последней позиции. Методы __setstate__() и __getstate__() используются для реализации этого поведения.
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
self.file = file
Пример использования может выглядеть следующим образом:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
Настройка сокращения для типов, функций и других объектов
New in version 3.8.
Иногда таблица диспетчеризации dispatch_table может оказаться недостаточно гибкой. В частности, мы можем захотеть настроить сериализацию на основе критерия, отличного от типа объекта, или настроить сериализацию функций и классов.
В таких случаях можно создать подкласс класса Pickler и реализовать метод reducer_override(). Этот метод может возвращать произвольный кортеж сокращения (см. __reduce__()). В качестве альтернативы, он может вернуть NotImplemented для возврата к традиционному поведению.
Если определены как таблица диспетчеризации dispatch_table, так и метод reducer_override(), тогда метод reducer_override() имеет приоритет.
Примечание
По соображениям производительности, метод reducer_override() может не вызываться для следующих объектов: None, True, False, и точных экземпляров int, float, bytes, str, dict, set, frozenset, list и tuple.
Вот простой пример, где мы разрешаем сериализацию и реконструирование данного класса:
import io
import pickle
class MyClass:
my_attribute = 1
class MyPickler(pickle.Pickler):
def reducer_override(self, obj):
"""Custom reducer for MyClass."""
if getattr(obj, "__name__", None) == "MyClass":
return type, (obj.__name__, obj.__bases__,
{'my_attribute': obj.my_attribute})
else:
# For any other object, fallback to usual reduction
return NotImplemented
f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)
del MyClass
unpickled_class = pickle.loads(f.getvalue())
assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1
Буферы вне полосы
В версии 3.8.
В некоторых контекстах модуль pickle используется для передачи больших объёмов данных. Поэтому важно свести к минимуму количество копирований в памяти, чтобы сохранить производительность и потребление ресурсов. Однако обычная работа модуля pickle, поскольку он преобразует структуру объектов в виде графа в последовательный поток байтов, по своей природе включает копирование данных в и из потока пиклирования.
Этот ограничитель можно обойти, если как поставщик (реализация типов объектов, которые должны передаваться), так и потребитель (реализация системы связи) поддерживают возможности передачи вне полосы, предоставляемые протоколом пиклирования 5 и выше.
API поставщика
Большие объекты данных, которые должны быть сериализованы, должны реализовывать метод __reduce_ex__() , специализированный для протоколов 5 и выше, который возвращает экземпляр PickleBuffer (вместо, например, объекта bytes) для любых больших данных.
Объект PickleBuffer указывает, что базовый буфер подходит для передачи данных вне полосы. Эти объекты остаются совместимыми с обычным использованием модуля pickle. Однако потребители также могут выбрать, чтобы сообщить pickle, что они будут обрабатывать эти буферы самостоятельно.
API потребителя
Система связи может включить пользовательскую обработку объектов PickleBuffer, созданных при сериализации графа объектов.
Со стороны отправки необходимо передать аргумент buffer_callback в Pickler (или в функции dump() или dumps()), который будет вызываться с каждым объектом PickleBuffer, созданным во время пиклирования графа объектов. Буферы, накопленные функцией buffer_callback, не будут видеть свои данные, скопированные в поток пиклирования, будет вставлен только дешёвый маркер.
Со стороны приема необходимо передать аргумент buffers в Unpickler (или в функции load() или loads()), который является итерируемым объектом буферов, которые были переданы в buffer_callback. Этот итерируемый объект должен генерировать буферы в том же порядке, в котором они были переданы в buffer_callback. Эти буферы предоставят данные, ожидаемые реконструкторами объектов, пиклирование которых привело к исходным объектам PickleBuffer.
Между сторонами отправки и приема система связи свободна реализовывать собственный механизм передачи буферов вне полосы. Возможные оптимизации включают использование общей памяти или сжатия, зависящего от типа данных.
Пример
Вот тривиальный пример, где мы реализуем подкласс bytearray, способный участвовать в пиклировании буферов вне полосы:
class ZeroCopyByteArray(bytearray):
def __reduce_ex__(self, protocol):
if protocol >= 5:
return type(self)._reconstruct, (PickleBuffer(self),), None
else:
# PickleBuffer is forbidden with pickle protocols <= 4.
return type(self)._reconstruct, (bytearray(self),)
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
# Get a handle over the original buffer object
obj = m.obj
if type(obj) is cls:
# Original buffer object is a ZeroCopyByteArray, return it
# as-is.
return obj
else:
return cls(obj)
Реконструктор (метод класса _reconstruct ) возвращает предоставляющий объект буфера, если у него правильный тип. Это простой способ смоделировать поведение нулевого копирования в этом примере.
Со стороны потребителя мы можем сохранить эти объекты обычным способом, что при десериализации даст нам копию исходного объекта:
b = ZeroCopyByteArray(b"abc") data = pickle.dumps(b, protocol=5) new_b = pickle.loads(data) print(b == new_b) # True print(b is new_b) # False: a copy was made
Но если мы передадим buffer_callback и затем вернём накопленные буферы при десериализации, мы сможем получить исходный объект:
b = ZeroCopyByteArray(b"abc") buffers = [] data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append) new_b = pickle.loads(data, buffers=buffers) print(b == new_b) # True print(b is new_b) # True: no copy was made
Этот пример ограничен тем, что bytearray выделяет собственную память: вы не можете создать экземпляр bytearray, который поддерживается памятью другого объекта. Однако сторонние типы данных, такие как массивы NumPy, не имеют этого ограничения и позволяют использовать пиклирование нулевого копирования (или делать как можно меньше копий) при передаче между различными процессами или системами.
См. также
PEP 574 – Протокол пиклирования 5 с данными вне полосы
Ограничение глобальных переменных
По умолчанию, десериализация импортирует любой класс или функцию, которые она находит в данных пиклирования. Для многих приложений это поведение неприемлемо, так как оно позволяет десериализатору импортировать и вызывать произвольный код. Просто представьте, что делает этот созданный вручную поток данных пиклирования при загрузке:
>>> import pickle >>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.") hello world 0
В этом примере десериализатор импортирует функцию os.system(), а затем применяет строковый аргумент «echo hello world». Хотя этот пример безвреден, легко представить себе пример, который может повредить вашу систему.
По этой причине вы можете захотеть контролировать то, что десериализуется, настроив Unpickler.find_class(). Вопреки своему названию, Unpickler.find_class() вызывается всякий раз, когда запрашивается глобальная переменная (т. е. класс или функция). Таким образом, можно либо полностью запретить глобальные переменные, либо ограничить их до безопасного подмножества.
Вот пример десериализатора, допускающего загрузку только нескольких безопасных классов из модуля builtins:
import builtins
import io
import pickle
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
Пример использования нашего десериализатора, работающего как ожидается:
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
... b'(S\'getattr(__import__("os"), "system")'
... b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'builtins.eval' is forbidden
Как показывают наши примеры, нужно быть осторожными с тем, что разрешается десериализовать. Поэтому, если безопасность является проблемой, вы можете рассмотреть альтернативы, такие как API маршалинга в xmlrpc.client или сторонние решения.
Производительность
Недавние версии протокола пиклирования (с протокола 2 и выше) имеют эффективные двоичные кодирования для нескольких общих функций и встроенных типов. Также модуль pickle имеет прозрачный оптимизатор, написанный на C.
Примеры
Для самого простого кода используйте функции dump() и load().
import pickle
# An arbitrary collection of objects supported by pickle.
data = {
'a': [1, 2.0, 3+4j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# Pickle the 'data' dictionary using the highest protocol available.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
Следующий пример считывает полученные данные pickle.
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
См. также
-
Modulecopyreg -
Регистрация конструкторов интерфейса pickle для типов расширений.
-
Modulepickletools -
Инструменты для работы с данными pickle и их анализа.
-
Moduleshelve -
Индексированные базы данных объектов; использует
pickle. -
Modulecopy -
Поверхностное и глубокое копирование объектов.
-
Modulemarshal -
Высокопроизводительная сериализация встроенных типов.
Примечания
-
1 -
Не путайте это с модулем
marshal -
2 -
Вот почему функции
lambdaнельзя сохранять в pickle: все функцииlambdaимеют одно имя:<lambda>. -
3 -
Возникающее исключение, вероятно, будет
ImportErrorилиAttributeError, но это может быть что-то другое. -
4 -
Модуль
copyиспользует этот протокол для поверхностного и глубокого копирования. -
5 -
Ограничение на буквенно-цифровые символы связано с тем, что идентификаторы постоянства в протоколе 0 разделяются символом новой строки. Поэтому, если в идентификаторах постоянства появляются символы новой строки, полученные данные pickle станут нечитаемыми.
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/pickle.html