pickle
— Сериализация объектов Python¶
Исходный код: Lib/pickle.py.
Модуль pickle
реализует двоичные протоколы для сериализации и де-сериализации объектной структуры Python. «Pickling « - это процесс преобразования иерархии объектов Python в поток байтов, а «unpickling « - обратная операция, при которой поток байтов (из binary file или bytes-like object) преобразуется обратно в иерархию объектов. Пиклинг (и распиклинг) альтернативно называют «сериализацией», «маршалингом», 1 или «сплющиванием»; однако, чтобы избежать путаницы, здесь используются термины «пиклинг» и «распиклинг».
Предупреждение
Модуль pickle
небезопасен. Распаковывайте только те данные, которым вы доверяете.
Можно создать вредоносные данные pickle, которые выполнят произвольный код во время распаковки. Никогда не распаковывайте данные, которые могли быть получены из ненадежного источника или могли быть подделаны.
Рассмотрите возможность подписания данных с помощью hmac
, если вам нужно убедиться, что они не были подделаны.
Более безопасные форматы сериализации, такие как json
, могут быть более подходящими, если вы обрабатываете недоверенные данные. См. Сравнение с json.
Взаимосвязь с другими модулями Python¶
Сравнение с marshal
¶
В Python есть более примитивный модуль сериализации marshal
, но в целом pickle
всегда должен быть предпочтительным способом сериализации объектов Python. marshal
существует в основном для поддержки файлов Python .pyc
.
Модуль 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; сложные случаи могут быть решены путем реализации specific object APIs);
В отличие от pickle, десериализация недоверенного JSON сама по себе не создает уязвимость выполнения произвольного кода.
См.также
Модуль json
: модуль стандартной библиотеки, обеспечивающий сериализацию и десериализацию JSON.
Формат потока данных¶
Формат данных, используемый pickle
, специфичен для Python. Преимуществом этого формата является отсутствие ограничений, налагаемых внешними стандартами, такими как JSON или XDR (которые не могут представлять совместное использование указателей); однако это означает, что не-Python программы не смогут восстановить маринованные Python объекты.
По умолчанию формат данных pickle
использует относительно компактное двоичное представление. Если вам нужны оптимальные характеристики размера, вы можете эффективно использовать compress маринованные данные.
Модуль pickletools
содержит инструменты для анализа потоков данных, генерируемых pickle
. Исходный код pickletools
содержит обширные комментарии об опкодах, используемых протоколами pickle.
В настоящее время существует 6 различных протоколов, которые могут быть использованы для маринования. Чем выше используемый протокол, тем более новая версия Python необходима для чтения созданного pickle.
Протокол версии 0 является оригинальным «человекочитаемым» протоколом и обратно совместим с более ранними версиями Python.
Протокол версии 1 - это старый двоичный формат, который также совместим с ранними версиями Python.
Протокол версии 2 был представлен в Python 2.3. Он обеспечивает гораздо более эффективную выборку new-style classes. См. раздел PEP 307 для получения информации об улучшениях, внесенных протоколом 2.
Протокол версии 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 version. Это значение может быть передано в качестве протокольного значения функциям
dump()
иdumps()
, а также конструкторуPickler
.
-
pickle.
DEFAULT_PROTOCOL
¶ Целое число, по умолчанию protocol version, используемое для травления. Может быть меньше, чем
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 object файл. Это эквивалентно команде
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 object файла и возвращает указанную в нем восстановленную иерархию объектов. Это эквивалентно
Unpickler(file).load()
.Версия протокола pickle определяется автоматически, поэтому аргумент 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.
Версия протокола pickle определяется автоматически, поэтому аргумент 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(), принимающий аргумент в виде одного байта. Таким образом, это может быть файл на диске, открытый для двоичной записи, экземпляр
io.BytesIO
или любой другой пользовательский объект, отвечающий этому интерфейсу.Если fix_imports равно true и protocol меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных pickle был доступен для чтения в Python 2.
Если buffer_callback равен None (по умолчанию), представления буфера сериализуются в file как часть потока pickle.
Если buffer_callback не None, то он может быть вызван любое количество раз с представлением буфера. Если обратный вызов возвращает ложное значение (например, None), то данный буфер будет out-of-band; в противном случае буфер сериализуется in-band, т.е. внутри потока 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
¶ Диспетчерская таблица объекта pickler - это реестр редукционных функций, которые могут быть объявлены с помощью
copyreg.pickle()
. Это отображение, ключами которого являются классы, а значениями - функции редукции. Функция уменьшения принимает один аргумент связанного класса и должна соответствовать тому же интерфейсу, что и метод__reduce__()
.По умолчанию объект pickler не будет иметь атрибута
dispatch_table
, и вместо него будет использоваться глобальная таблица диспетчеризации, управляемая модулемcopyreg
. Однако, чтобы настроить пиклинг для конкретного объекта pickler, можно установить атрибутdispatch_table
на диктоподобный объект. В качестве альтернативы, если подклассPickler
имеет атрибутdispatch_table
, то он будет использоваться в качестве таблицы диспетчеризации по умолчанию для экземпляров этого класса.Примеры использования см. в разделе Диспетчерские столы.
Добавлено в версии 3.3.
-
reducer_override
(obj)¶ Специальный редуктор, который может быть определен в подклассах
Pickler
. Этот метод имеет приоритет над любым редуктором вdispatch_table
. Он должен соответствовать тому же интерфейсу, что и метод__reduce__()
, и может опционально возвращатьNotImplemented
для отката наdispatch_table
-регистрированные редукторы для выборкиobj
.Подробный пример см. в разделе Пользовательское сокращение для типов, функций и других объектов.
Добавлено в версии 3.8.
-
fast
¶ Исправлено. Включает быстрый режим, если установлено в значение true. Быстрый режим отключает использование memo, что ускоряет процесс пикинга, не генерируя лишних опкодов 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 равен true, pickle будет пытаться сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Параметры encoding и errors указывают pickle, как декодировать 8-битные экземпляры строк, собранные Python 2; по умолчанию они имеют значения „ASCII“ и „strict“ соответственно. Кодировка* может быть „bytes“ для чтения этих 8-битных строк как байтовых объектов. Использование
encoding='latin1'
требуется для распаковки массивов NumPy и экземпляровdatetime
,date
иtime
, распакованных Python 2.Если buffers равен None (по умолчанию), то все данные, необходимые для десериализации, должны содержаться в потоке pickle. Это означает, что аргумент buffer_callback был None при инстанцировании
Pickler
(или при вызовеdump()
илиdumps()
).Если buffers не None, то это должна быть итерабель объектов с поддержкой буферов, которая потребляется каждый раз, когда поток pickle ссылается на представление буфера out-of-band. Такие буферы передаются в порядке buffer_callback объекта Pickler.
Изменено в версии 3.8: Был добавлен аргумент buffers.
-
load
()¶ Считывает маринованное представление объекта из объекта открытого файла, заданного в конструкторе, и возвращает восстановленную иерархию объектов, указанную в ней. Байты после пикелированного представления объекта игнорируются.
-
persistent_load
(pid)¶ По умолчанию вызывает
UnpicklingError
.Если определено,
persistent_load()
должен вернуть объект, указанный постоянным идентификатором pid. Если встречается недопустимый постоянный идентификатор, должен быть вызван сигналUnpicklingError
.Подробности и примеры использования смотрите в Постоянство внешних объектов.
-
find_class
(module, name)¶ При необходимости импортируйте модуль и верните из него объект с именем name, где аргументы модуль и name являются объектами
str
. Обратите внимание, что, в отличие от названия,find_class()
также используется для поиска функций.Подклассы могут переопределить его, чтобы получить контроль над тем, какие типы объектов и как они могут быть загружены, потенциально снижая риски безопасности. Подробности см. в Ограничение глобальных файлов.
Вызывает auditing event
pickle.find_class
с аргументамиmodule
,name
.
-
-
class
pickle.
PickleBuffer
(buffer)¶ Обертка для буфера, представляющего пиклируемые данные. buffer должен быть объектом buffer-providing, таким как bytes-like object или N-мерный массив.
PickleBuffer
сам является поставщиком буфера, поэтому его можно передавать другим API, ожидающим объект, предоставляющий буфер, напримерmemoryview
.Объекты
PickleBuffer
могут быть сериализованы только с использованием протокола pickle 5 или выше. Они могут использоваться для out-of-band serialization.Добавлено в версии 3.8.
-
raw
()¶ Возвращает
memoryview
области памяти, лежащей в основе данного буфера. Возвращаемый объект является одномерным, C-контигиальным представлением памяти с форматомB
(беззнаковые байты). Если буфер не является ни C-, ни Fortran-континуальным, то возникает ошибкаBufferError
.
-
release
()¶ Освобождение базового буфера, открываемого объектом PickleBuffer.
-
Что можно мариновать и не мариновать?¶
Следующие виды можно мариновать:
None
,True
иFalse
;целые числа, числа с плавающей точкой, комплексные числа;
строки, байты, байтовые массивы;
кортежи, списки, множества и словари, содержащие только picklable объекты;
функции (встроенные и определяемые пользователем), доступные с верхнего уровня модуля (с использованием
def
, а неlambda
);классы, доступные с верхнего уровня модуля;
экземпляры таких классов, чьи
__dict__
или результат вызова__getstate__()
являются picklable (подробнее см. раздел Экземпляры класса пикелевания).
Попытки пикетировать непиклируемые объекты вызовут исключение PicklingError
; когда это произойдет, неопределенное количество байт может быть уже записано в базовый файл. Попытка выбрать сильно рекурсивную структуру данных может превысить максимальную глубину рекурсии, в этом случае будет вызвано исключение RecursionError
. Вы можете осторожно поднять этот предел с помощью sys.setrecursionlimit()
.
Обратите внимание, что функции (встроенные и определяемые пользователем) травятся по полному qualified name, а не по значению. 2 Это означает, что травится только имя функции, а также имя содержащего модуля и классов. Ни код функции, ни ее атрибуты не травятся. Таким образом, определяющий модуль должен быть импортируемым в среду распикировки, а модуль должен содержать именованный объект, иначе будет вызвано исключение. 3
Аналогично, классы травятся по полностью определенному имени, поэтому в среде распаковки действуют те же ограничения. Обратите внимание, что ни код, ни данные класса не травятся, поэтому в следующем примере атрибут класса attr
не восстанавливается в среде распикировки:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
Именно из-за этих ограничений picklable функции и классы должны быть определены на верхнем уровне модуля.
Аналогично, когда экземпляры классов маринуются, код и данные их класса не маринуются вместе с ними. Маринуются только данные экземпляра. Это сделано специально, чтобы вы могли исправлять ошибки в классе или добавлять методы в класс и при этом загружать объекты, созданные с помощью более ранней версии класса. Если вы планируете иметь долгоживущие объекты, которые увидят много версий класса, возможно, стоит поместить номер версии в объекты, чтобы соответствующие преобразования могли быть сделаны методом __setstate__()
класса.
Экземпляры класса пикелевания¶
В этом разделе мы опишем общие механизмы, доступные вам для определения, настройки и управления тем, как экземпляры класса маринуются и распаковываются.
В большинстве случаев для того, чтобы сделать экземпляры picklable, не требуется никакого дополнительного кода. По умолчанию 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 в протоколах 2 и 3 вместо
__getnewargs__()
вызывалось__getnewargs_ex__()
.
-
object.
__getstate__
()¶ Классы могут дополнительно влиять на то, как их экземпляры травятся; если класс определяет метод
__getstate__()
, он вызывается, и возвращаемый объект травится как содержимое экземпляра, вместо содержимого словаря экземпляра. Если метод__getstate__()
отсутствует, то экземпляр__dict__
маринуется как обычно.
-
object.
__setstate__
(state)¶ При распаковке, если класс определяет
__setstate__()
, он вызывается с распакованным состоянием. В этом случае нет требования, чтобы объект state был словарем. В противном случае, распакованное состояние должно быть словарем, и его элементы присваиваются словарю нового экземпляра.Примечание
Если
__getstate__()
возвращает значение false, метод__setstate__()
не будет вызван при распикировке.
Обратитесь к разделу Работа с государственными объектами для получения дополнительной информации о том, как использовать методы __getstate__()
и __setstate__()
.
Примечание
Во время распикировки к экземпляру могут быть вызваны некоторые методы, такие как __getattr__()
, __getattribute__()
или __setattr__()
. Если эти методы полагаются на истинность некоторого внутреннего инварианта, тип должен реализовать __new__()
для установления такого инварианта, поскольку __init__()
не вызывается при распаковке экземпляра.
Как мы увидим, pickle не использует напрямую методы, описанные выше. Фактически, эти методы являются частью протокола копирования, который реализует специальный метод __reduce__()
. Протокол копирования предоставляет унифицированный интерфейс для получения данных, необходимых для пикирования и копирования объектов. 4
Несмотря на свою мощь, реализация __reduce__()
непосредственно в ваших классах чревата ошибками. По этой причине разработчики классов должны использовать высокоуровневый интерфейс (т.е. __getnewargs_ex__()
, __getstate__()
и __setstate__()
) всегда, когда это возможно. Однако мы покажем случаи, когда использование __reduce__()
является единственным вариантом или приводит к более эффективному травлению, или и то, и другое.
-
object.
__reduce__
()¶ В настоящее время интерфейс определен следующим образом. Метод
__reduce__()
не принимает аргументов и возвращает либо строку, либо, предпочтительно, кортеж (возвращаемый объект часто называют «уменьшенным значением»).Если возвращается строка, ее следует интерпретировать как имя глобальной переменной. Это должно быть локальное имя объекта относительно его модуля; модуль pickle ищет в пространстве имен модуля, чтобы определить модуль объекта. Такое поведение обычно полезно для синглтонов.
Когда возвращается кортеж, его длина должна составлять от двух до шести элементов. Необязательные элементы могут быть либо опущены, либо в качестве их значения может быть указано
None
. Семантика каждого элемента упорядочена:Вызываемый объект, который будет вызван для создания начальной версии объекта.
Кортеж аргументов для объекта callable. Пустой кортеж должен быть указан, если вызываемый объект не принимает ни одного аргумента.
Опционально, состояние объекта, которое будет передано в метод
__setstate__()
объекта, как описано ранее. Если у объекта нет такого метода, то значение должно быть словарем, и оно будет добавлено к атрибуту__dict__
объекта.Опционально, итератор (а не последовательность), дающий последовательные элементы. Эти элементы будут добавлены к объекту либо с помощью
obj.append(item)
, либо, пакетно, с помощьюobj.extend(list_of_items)
. Эта функция используется в основном для подклассов списков, но может использоваться и другими классами, если у них есть методыappend()
иextend()
с соответствующей сигнатурой. (Используется лиappend()
илиextend()
, зависит от версии протокола pickle, а также от количества добавляемых элементов, поэтому оба метода должны поддерживаться).Опционально, итератор (не последовательность), дающий последовательные пары ключ-значение. Эти элементы будут сохранены в объекте с помощью
obj[key] = value
. Это в основном используется для подклассов словаря, но может использоваться и другими классами, если они реализуют__setitem__()
.Опционально, вызываемый объект с сигнатурой
(obj, state)
. Эта вызываемая переменная позволяет пользователю программно контролировать поведение обновления состояния конкретного объекта, вместо использования статического методаobj
__setstate__()
. Если неNone
, эта вызываемая переменная будет иметь приоритет надobj
’s__setstate__()
.Добавлено в версии 3.8: Был добавлен необязательный шестой элемент кортежа,
(obj, state)
.
-
object.
__reduce_ex__
(protocol)¶ В качестве альтернативы может быть определен метод
__reduce_ex__()
. Единственное отличие заключается в том, что этот метод должен принимать один целочисленный аргумент - версию протокола. Если он определен, pickle предпочтет его методу__reduce__()
. Кроме того,__reduce__()
автоматически становится синонимом расширенной версии. Основное применение этого метода - обеспечение обратно совместимых значений уменьшения для старых версий Python.
Постоянство внешних объектов¶
В целях обеспечения сохранности объектов модуль pickle
поддерживает понятие ссылки на объект вне потока травленых данных. На такие объекты ссылается постоянный идентификатор, который должен быть либо строкой буквенно-цифровых символов (для протокола 0) 5, либо просто произвольным объектом (для любого более нового протокола).
Разрешение таких постоянных идентификаторов не определяется модулем pickle
; он делегирует это разрешение определяемым пользователем методам на pickler и unpickler, persistent_id()
и persistent_load()
соответственно.
Чтобы травить объекты, имеющие внешний постоянный идентификатор, pickler должен иметь пользовательский метод persistent_id()
, который принимает объект в качестве аргумента и возвращает либо None
, либо постоянный идентификатор этого объекта. Если возвращается None
, пиклер просто пикетирует объект, как обычно. Если возвращается строка постоянного идентификатора, пиклер будет пикетировать этот объект вместе с маркером, чтобы распаковщик распознал его как постоянный идентификатор.
Для распаковки внешних объектов распаковщик должен иметь пользовательский метод persistent_load()
, который принимает постоянный объект ID и возвращает ссылающийся объект.
Ниже приведен исчерпывающий пример, показывающий, как постоянный идентификатор можно использовать для маринования внешних объектов по ссылке.
# 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
.
Работа с государственными объектами¶
Вот пример, показывающий, как изменить поведение pickling для класса. Класс TextReader
открывает текстовый файл и возвращает номер строки и содержимое строки каждый раз, когда вызывается его метод readline()
. Если экземпляр TextReader
замаринован, все атрибуты, за исключением члена объекта file, сохраняются. Когда экземпляр распаковывается, файл открывается снова, и чтение возобновляется с последнего места. Для реализации этого поведения используются методы __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!'
Пользовательское сокращение для типов, функций и других объектов¶
Добавлено в версии 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
, поскольку он преобразует графоподобную структуру объектов в последовательный поток байтов, неизбежно включает копирование данных в поток 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 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, не имеют этого ограничения и позволяют использовать zero-copy pickling (или делать как можно меньше копий) при передаче между различными процессами или системами.
См.также
PEP 574 – Pickle протокол 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):
# 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
или решения сторонних производителей.
Производительность¶
Последние версии протокола pickle (начиная с протокола 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)
В следующем примере читаются полученные маринованные данные.
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)
См.также
- Модуль
copyreg
Регистрация конструктора интерфейса Pickle для типов расширения.
- Модуль
pickletools
Инструменты для работы с травлеными данными и их анализа.
- Модуль
shelve
Индексированные базы данных объектов; использует
pickle
.- Модуль
copy
Мелкое и глубокое копирование объектов.
- Модуль
marshal
Высокопроизводительная сериализация встроенных типов.
Сноски
- 1
Не путайте с модулем
marshal
.- 2
Вот почему функции
lambda
нельзя пикировать: все функцииlambda
имеют одно и то же имя:<lambda>
.- 3
Вызванное исключение, скорее всего, будет
ImportError
илиAttributeError
, но это может быть и что-то другое.- 4
Модуль
copy
использует этот протокол для операций поверхностного и глубокого копирования.- 5
Ограничение на буквенно-цифровые символы связано с тем, что постоянные идентификаторы в протоколе 0 разделены символом новой строки. Поэтому, если в постоянных идентификаторах встретятся символы новой строки любого вида, результирующие травленые данные станут нечитаемыми.