pickle
— Сериализация Python объекта¶
Модуль pickle
реализует двоичные протоколы для сериализации и
десериализации структуры Python объекта. «Пиклинг» — это процесс,
посредством которого иерархия Python объекта преобразуется в поток байтов, а
«анпиклинг» — это обратная операция, посредством которой поток байтов (из
двоичного файла или
байтоподобного объекта) преобразуется обратно в
иерархию объекта. Пиклинг (и анпиклинг) также известно как «сериализация»,
«маршаллинг», [1] или «сплющивание»; однако, чтобы избежать путаницы,
используются термины «пиклинг» и «анпиклинг».
Предупреждение
Модуль pickle
не безопаcен. Анпиклите только те данные, которым
доверяете.
Можно создавать вредоносные pickle данные, которые будут выполнять произвольный код во время анпиклинга. Никогда не анпиклите данные, которые могли быть получены из ненадежного источника или в которые могли быть внесены изменения.
Рассмотрите возможность подписания данных с помощью hmac
, если вам нужно
убедиться, что они не были подделаны.
Безопасные форматы сериализации, такие как json
, могут быть более
подходящими, если вы обрабатываете ненадежные данные. См.
Сравнение с json.
Отношение к другим Python модулям¶
Сравнение с marshal
¶
У Python есть более примитивный модуль сериализации под названием marshal
,
но в целом pickle
всегда должен быть предпочтительным способом сериализации
Python объектов. marshal
существует в первую очередь для поддержки
.pyc
файлов Python .
Модуль pickle
отличается от marshal
несколькими существенными
особенностями:
Модуль
pickle
отслеживает объекты, которые он уже сериализованы, так что последующие ссылки на тот же объект больше не будут сериализованы.marshal
этого не делает.Это имеет значение как для рекурсивных объектов, так и для совместно используемых объектов. Рекурсивные объекты — это объекты, которые содержат ссылки на самих себя. Они не обрабатываются маршалингом, и на самом деле попытка маршалинга рекурсивных объектов приведет к сбою интерпретатора Python. Совместное использование объекта происходит, когда есть несколько ссылок на один и тот же объект в разных местах сериализуемой иерархии объектов.
pickle
сохраняет такие объекты только один раз и гарантирует, что все другие ссылки указывают на главную копию. Общие объекты остаются общими, что может быть очень важно для изменяемых объектов.marshal
нельзя использовать для сериализации пользовательских классов и их экземпляров.pickle
может сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при сохранении объекта.Переносимость формата сериализации
marshal
между версиями Python не гарантируется. Поскольку его основная задача заключается в поддержке.pyc
файлов, разработчики Python оставляют за собой право изменять формат сериализации несовместимыми способами, если возникнет такая необходимость. Формат сериализацииpickle
гарантированно обратно совместим между Python релизами при условии, что выбран совместимый протокол pickle, а код пиклинга и анпиклинга работает с различиями типов Python 2 и Python 3, если ваши данные пересекают эту уникальную границу языковых изменений.
Сравнение с json
¶
Между протоколами pickle и JSON (JavaScript Object Notation): существуют фундаментальные различия:
- JSON — это формат сериализации текста (он выводит текст в формате Юникод, хотя
большую часть времени он закодирован в
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
использует относительно компактное
двоичное представление. Если вам нужны оптимальные размерные характеристики, вы
можете эффективно пиклить сжатые данные.
Модуль 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. Обратитесь к PEP 3154 для получения информации об улучшениях, внесенных в протокол 4.
- Протокол версии 5 был добавлен в Python 3.8. Он добавляет поддержку внеполосных данных и ускорение внутриполосных данных. Обратитесь к PEP 574 для получения информации об улучшениях, внесенных в протокол 5.
Примечание
Сериализация — более примитивное понятие, чем постоянство; хотя 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()
.Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется. Байты после пиклинга представления объекта игнорируются.
Аргументы 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 должен быть байтоподобным объектом.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется. Байты после пиклинга представления объекта игнорируются.
Аргументы file, 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(), который принимает аргумент в виде одного байта. Таким образом, это может быть файл на диске, открытый для двоичной записи, экземпляр
io.BytesIO
или любой другой настраиваемый объект, который соответствует этому интерфейсу.Если fix_imports — истина, а 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
выдавать возвращаемое значение как постоянный ID для obj. Значение этого постоянного ID’а должно определятьсяUnpickler.persistent_load()
. Обратите внимание, что значение, возвращаемоеpersistent_id()
, не может иметь постоянного ID.Подробности и примеры использования см. в Постоянство внешних объектов.
-
dispatch_table
¶ Таблица диспетчеризации пиклер объекта представляет собой реестр редукционных функций, который может быть объявлен с помощью
copyreg.pickle()
. Это отображение, ключи которого являются классами, а значения — функциями редукции. Функция редукции принимает единственный аргумент связанного класса и должна соответствовать тому же интерфейсу, что и метод__reduce__()
.По умолчанию у пикл объекта не будет атрибута
dispatch_table
, а вместо него будет использоваться глобальная таблица диспетчеризации, управляемую модулемcopyreg
. Однако, чтобы настроить пиклинг для объекта средства выбора, можно установить атрибутdispatch_table
для объекта типа dict. В качестве альтернативы, если у подклассаPickler
есть атрибутdispatch_table
, то он будет использоваться в качестве таблицы диспетчеризации по умолчанию для экземпляров данного класса.Примеры использования см. в Таблицы диспетчеризации.
Добавлено в версии 3.3.
-
reducer_override
(self, obj)¶ Специальный редюсер, который может быть определен в подклассах
Pickler
. Этот метод имеет приоритет над любым редюсером вdispatch_table
. Он должен соответствовать тому же интерфейсу, что и метод__reduce__()
, и может при желании возвращатьNotImplemented
для возврата на зарегистрированныеdispatch_table
редюсеры для pickleobj
.Подробный пример см. в Пользовательская редукция для типов, функций и других объектов.
Добавлено в версии 3.8.
-
fast
¶ Не рекомендуется. Включить быстрый режим, если установлено истинное значение. Быстрый режим отключает использование памятки, тем самым ускоряя процесс пиклинга, не создавая лишних опкодов PUT. Его не следует использовать с самореференциальными объектами, в противном случае
Pickler
будет рекурсивно повторяться.Если вам нужны более компактные пикли, используйте
pickletools.optimize()
.
-
-
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 может быть «байтовым» для чтения этих 8-битных экземпляров строки как байтовых объектов. Использование
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()
должен возвращать объект, указанный постоянным ID pid. Если обнаружен недопустимый постоянный ID, должно вызватьсяUnpicklingError
.Подробности и примеры использования см. в Постоянство внешних объектов.
-
find_class
(module, name)¶ Импортирует module, если необходимо, и возвращет из него объект с именем name, где аргументы module и name являются объектами
str
. Обратите внимание: в отличие от названия,find_class()
также используется для поиска функций.Подклассы могут переопределить это, чтобы получить контроль над типом объектов и тем, как они могут быть загружены, потенциально снижая риски безопасности. За подробностями обращайтесь к Глобальное ограничение.
Вызывает событие аудита
pickle.find_class
с аргументамиmodule
,name
.
-
-
class
pickle.
PickleBuffer
(buffer)¶ Обёртка для буфера, представляющего пиклингуемые данные. buffer должен быть буферным объектом, например байтоподобный объект или N-мерным массивом.
PickleBuffer
сам по себе является поставщиком буфера, поэтому его можно передать другим API, ожидающим объекта, предоставляющего буфер, напримерmemoryview
.Объекты
PickleBuffer
можно сериализовать только с использованием протокола pickle 5 или выше. Они имеют право на внеполосную сериализацию.Добавлено в версии 3.8.
-
raw
()¶ Возвращает
memoryview
области памяти, лежащей в основе этого буфера. Возвращенный объект представляет собой одномерное непрерывное представление памяти C в форматеB
(беззнаковые байты). ВызываетсяBufferError
, если буфер не является ни C, ни Fortran смежным.
-
release
()¶ Освобождает нижележащий буфер, предоставленный PickleBuffer объектом.
-
Что можно пиклить, а что нет?¶
Можно пиклить следующие типы:
None
,True
иFalse
- Целые числа, числа с плавающей запятой, комплексные числа
- Строки, байты, байтовые массивы
- кортежи, списки, множества и словари, содержащие только пиклингуемые объекты
- функции, определенные на верхнем уровне модуля (с использованием
def
, а неlambda
) - встроенные функции, определенные на верхнем уровне модуля
- классы, определенные на верхнем уровне модуля
- экземпляры таких классов, у которых
__dict__
или результат вызова__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 load(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__()
, он вызывается, и возвращаемый объект обрабатывается как содержимое для экземпляра, а не как содержимое словаря экземпляра. Если метод__getstate__()
отсутствует,__dict__
экземпляра пиклится как обычно.
-
object.
__setstate__
(state)¶ Если при анпиклинге класс определяет
__setstate__()
, он вызывается с непикленном состоянием. В этом случае не требуется, чтобы объект состояния был словарем. В противном случае пиклед состояние должно быть словарём, а его элементы назначаются словарю нового экземпляра.Примечание
Если
__getstate__()
возвращает ложное значение, метод__setstate__()
не будет вызван при анпиклинге.
Обратитесь к разделу Обработка объектов с сохранением состояния для получения дополнительной информации о том,
как использовать методы __getstate__()
и __setstate__()
.
Примечание
Во время анпиклинга экземпляра могут быть вызваны такие методы, как __getattr__()
,
__getattribute__()
или __setattr__()
. В случае, если эти методы полагаются на истинность
некоторого внутреннего инварианта, тип должен реализовывать __new__()
, чтобы
установить такой инвариант, поскольку __init__()
не вызывается при анпиклинге
экземпляра.
Как мы увидим, pickle не использует описанные выше методы напрямую.
Фактически, эти методы являются частью протокола копирования, который реализует
специальный метод __reduce__()
. Протокол копирования предоставляет
унифицированный интерфейс для получения данных, необходимых для пиклинга и
копирования объектов. [4]
Несмотря на свою мощь, реализация __reduce__()
непосредственно в ваших классах
подвержена ошибкам. По этой причине разработчики классов должны по возможности
использовать высокоуровневый интерфейс (например, __getnewargs_ex__()
, __getstate__()
и
__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)
. Вызываемый объект позволяет пользователю программно управлять поведением обновления состояния объекта вместо использования статического метода__setstate__()
obj
. Если неNone
, вызываемый объект будет иметь приоритет над__setstate__()
obj
.Добавлено в версии 3.8: Добавлен необязательный шестой элемент кортежа
(obj, state)
.
-
object.
__reduce_ex__
(protocol)¶ В качестве альтернативы можно определить метод
__reduce_ex__()
. Единственная разница в том, что метод должен принимать единственный целочисленный аргумент — версию протокола. Когда определено, pickle предпочтёт его методу__reduce__()
. Кроме того,__reduce__()
автоматически становится синонимом расширенной версии. Основное использование этого метода — предоставление обратно совместимых сокращенных значений для старых версий Python.
Постоянство внешних объектов¶
Для обеспечения постоянства объекта модуль pickle
поддерживает понятие
ссылки на объект вне потока пиклинга данных. На такие объекты ссылается
постоянный ID, который должен быть либо строкой буквенно-цифровых
символов (для протокола 0) [5], либо просто произвольным объектом (для любого
нового протокола).
Разрешение таких постоянных ID не определяется модулем
pickle
; он делегирует это разрешение определенным пользователем методам
средства выбора и удаления, persistent_id()
и persistent_load()
соответственно.
Чтобы пиклить объекты с внешним постоянным ID, пиклер должно содержать
собственный метод persistent_id()
, который
принимает объект в качестве аргумента и возвращает либо None
, либо
постоянный ID этого объекта. Когда возвращается None
, пиклер просто пиклит
объект как обычно. Когда возвращается ID строки, пиклер
пиклит этот объект вместе с маркером, чтобы анпиклер распознавал его как постоянный ID.
Чтобы анпиклить внешние объекты, у анпиклера должен быть специальный метод
persistent_load()
, который принимает постоянный ID объекта и
возвращает объект, на который имеется ссылка.
Вот исчерпывающий пример, показывающий, как постоянный ID может использоваться для пиклинга внешних объектов по ссылке.
# Простой пример, показывающий, как постоянный идентификатор может использоваться
# для выбора внешних объектов по ссылке.
import pickle
import sqlite3
from collections import namedtuple
# Простой класс, представляющий запись в нашей базе данных.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Вместо того, чтобы пиклить MemoRecord как обычный экземпляр класса, мы
# генерируем постоянный ID.
if isinstance(obj, MemoRecord):
# Здесь наш постоянный ID --- это просто кортеж, содержащий тег и
# ключ, который относится к определенной записи в базе данных.
return ("MemoRecord", obj.key)
else:
# Если obj не имеет постоянного ID, вернуть None. Это означает, что
# obj нужно пиклить как обычно.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# Этот метод вызывается всякий раз, когда встречается постоянный ID.
# Здесь pid - это кортеж, возвращаемый DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Извлечь указанную запись из базы данных и вернуть её.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Всегда вызывает ошибку, если вы не можете вернуть правильный объект. В
# противном случае анпиклер будет думать, что None --- это объект, на который
# ссылается постоянный ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Инициализировать и заполнить нашу базу данных.
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,))
# Выбрать записи для пиклинга.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Сохранить записи с помощью нашего настраиваемого DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Вернуть запись, на всякий случай.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Загрузить записи из потока данных pickle.
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
:
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
Обработка объектов с сохранением состояния¶
Вот пример, показывающий, как изменить поведение пиклинга для класса. Класс
TextReader
открывает текстовый файл и возвращает номер строки и её содержимое
каждый раз, когда вызывается его метод readline()
. Если экземпляр TextReader
пиклед, все атрибуты кроме члена файлового объекта сохраняются. Когда
экземпляр анпиклед, файл открывается повторно, и чтение возобновляется с
последнего места. Для реализации этого поведения используются методы __setstate__()
и __getstate__()
.
class TextReader:
"""Распечатать и пронумеровать строки в текстовом файле."""
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):
# Скопировать состояние объекта из self.__ dict__, который содержит все атрибуты
# нашего экземпляра. Всегда используйте метод dict.copy(), чтобы избежать
# изменения исходного состояния.
state = self.__dict__.copy()
# Удалить непикабл записи.
del state['file']
return state
def __setstate__(self, state):
# Восстановить атрибуты экземпляра (например, имя файла и текстуру).
self.__dict__.update(state)
# Восстановление состояние ранее открытого файла. Для этого нам нужно снова
# открыть его и читать из него, пока не будет восстановлено количество строк.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# В завершение, сохранить файл.
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!'
Пользовательская редукция для типов, функций и других объектов¶
Добавлено в версии 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):
"""Пользовательский редьюсер для MyClass."""
if getattr(obj, "__name__", None) == "MyClass":
return type, (obj.__name__, obj.__bases__,
{'my_attribute': obj.my_attribute})
else:
# Для любого другого объекта возвращение к обычному сокращению
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
, поскольку он преобразует графоподобную
структуру объектов в последовательный поток байтов, по сути включает копирование
данных в и из pickle потока.
Этого ограничения можно избежать, если и провайдер (реализация типов объектов для передачи), и потребитель (реализация системы связи) поддерживают средства внеполосной передачи, обеспечиваемые протоколом pickle 5 и выше.
API провайдера¶
Большие объекты данных, которые должны быть обработаны, должны реализовывать
метод __reduce_ex__()
, специализированный для протокола 5 и выше, который возвращает
экземпляр PickleBuffer
(вместо, например, объекта bytes
) для любых больших
данных.
Объект PickleBuffer
сигнализирует, что нижележащий буфер имеет право на внеполосную
передачу данных. Эти объекты остаются совместимыми при нормальном использовании
модуля pickle
. Однако потребители также могут указать pickle
, что они
будут обрабатывать эти буферы самостоятельно.
Потребительское API¶
Система связи может обеспечивать возможность индивидуальной обработки объектов
PickleBuffer
, генерированных при сериализации графа объектов.
На стороне отправителя ему необходимо передать аргумент buffer_callback в
Pickler
(или в функцию dump()
или dumps()
), которая будет
вызываться с каждым PickleBuffer
, генерированным при обработке графа объекта.
Буферы, накопленные buffer_callback, не будут видеть свои данные, скопированные в
поток pickle, будет вставлен только дешевый маркер.
На принимающей стороне ему необходимо передать аргумент 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 запрещен с протоколами pickle <= 4.
return type(self)._reconstruct, (bytearray(self),)
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
# Получить дескриптор исходного объекта буфера
obj = m.obj
if type(obj) is cls:
# Исходный буферный объект - это ZeroCopyByteArray, вернуть его
# как есть.
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: копия была сделана
Но если мы передадим 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: копия не была сделана
Этот пример ограничен тем фактом, что bytearray
выделяет свою собственную
память: вы не можете создать экземпляр bytearray
, который поддерживается
памятью другого объекта. Однако сторонние типы данных, такие как массивы NumPy,
не содержат этого ограничения и позволяют использовать пиклинг с нулевым
копированием (или создание как можно меньшего количества копий) при передаче
между отдельными процессами или системами.
См.также
PEP 574 – протокол 5 Pickle с данными вне канала
Глобальное ограничение¶
По умолчанию анпиклинг импортирует любой класс или функцию, которые он найдет в пикл данных. Для многих приложений такое поведение неприемлемо, поскольку оно позволяет программе анпиклера импортировать и вызывать произвольный код. Просто подумайте, что делает этот вручную созданный поток данных pickle при загрузке:
>>> 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):
# Разрешить только безопасные классы из встроенных.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Запретить все остальное.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Вспомогательная функция, аналогичная 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
или сторонние
решения.
Производительность¶
Последние версии протокола pickle (начиная с протокола 2 и выше) содержат
эффективные двоичные кодировки для нескольких общих функций и встроенных типов.
Также модуль pickle
имеет прозрачный оптимизатор, написанный на C.
Примеры¶
Для простейшего кода используйте функции dump()
и load()
.
import pickle
# Произвольный набор объектов, поддерживаемый pickle.
data = {
'a': [1, 2.0, 3, 4+6j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# Пикл словаря «данных», используя самый высокий из доступных протоколов.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
В следующем примере читаются полученные в результате пиклед данные.
import pickle
with open('data.pickle', 'rb') as f:
# Используемая версия протокола определяется автоматически,
# поэтому указывать её не нужно.
data = pickle.load(f)
См.также
- Module
copyreg
- Регистрация конструктора интерфейса Pickle для типов расширений.
- Модуль
pickletools
- Инструменты для работы и анализа пиклед данных.
- Модуль
shelve
- Индексированные базы данных объектов; использует
pickle
. - Модуль
copy
- Поверхностное и глубокое копирование объектов.
- Модуль
marshal
- Высокопроизводительная сериализация встроенных типов.
Сноски
[1] | Не путайте с модулем marshal |
[2] | Вот почему функции lambda не могут быть пиклед: все
функции lambda имеют одно и то же имя: <lambda> . |
[3] | Вызванное исключение, вероятно, будет ImportError или
AttributeError но это может быть что-то другое. |
[4] | Модуль copy использует этот протокол для операций
поверхностного и глубокого копирования. |
[5] | Ограничение на буквенно-цифровые символы связано с тем, что постоянные ID’ы, в протоколе 0, ограничиваются символом новой строки. Поэтому, если в постоянных ID’ов встречаются какие-либо символы новой строки, результирующий пикл станет нечитаемым. |