pickle
— Сериализация объектов Python¶
Исходный код: Lib/pickle.py
Модуль pickle
реализует двоичные протоколы для сериализации и десериализации структуры объектов Python. * «Разбиение»* - это процесс, при котором иерархия объектов Python преобразуется в поток байтов, а «разбиение» - это обратная операция, при которой поток байтов (из binary file или bytes-like object) преобразуется обратно в объект. иерархия. Травление (и распаковка) также известно как «сериализация», «сортировка», [1] или «выравнивание»; однако, чтобы избежать путаницы, здесь используются термины «травление» и «распаковка».
Предупреждение
Модуль 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 необходима для считывания полученного маринада.
Протокол версии 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. Она добавляет поддержку внешних данных и ускоряет передачу данных в диапазоне. Информацию об улучшениях, внесенных протоколом 5, смотрите в PEP 574.
Примечание
Сериализация - более примитивное понятие, чем персистентность; хотя 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 определяется автоматически, поэтому никаких аргументов протокола не требуется. Байты, превышающие pickled-представление объекта, игнорируются.
Аргументы file, fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе
Unpickler
.Изменено в версии 3.8: Был добавлен аргумент buffers.
- pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
Возвращает восстановленную иерархию объектов выбранного представления данных объекта. данные должны быть bytes-like object.
Версия протокола pickle определяется автоматически, поэтому никаких аргументов протокола не требуется. Байты, превышающие pickled-представление объекта, игнорируются.
Аргументы fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе
Unpickler
.Изменено в версии 3.8: Был добавлен аргумент buffers.
Модуль pickle
определяет три исключения:
- exception pickle.PickleError¶
Общий базовый класс для других исключений pickling. Он наследуется от
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 как часть потока выбора.
Если значение buffer_callback не равно None, то оно может вызываться любое количество раз с использованием представления буфера. Если обратный вызов возвращает значение false (например, None), то заданный буфер равен out-of-band; в противном случае буфер сериализуется внутриполосно, т.е. внутри потока pickle.
Это ошибка, если значение buffer_callback не равно None, а значение protocol равно None или меньше 5.
Изменено в версии 3.8: Был добавлен аргумент buffer_callback.
- dump(obj)¶
Запишите выбранное представление obj в объект open file, указанный в конструкторе.
- persistent_id(obj)¶
По умолчанию ничего не делается. Это существует для того, чтобы подкласс мог его переопределить.
Если
persistent_id()
возвращаетNone
, то obj выбирается как обычно. Любое другое значение приводит к тому, чтоPickler
выдает возвращаемое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно быть определено с помощьюUnpickler.persistent_load()
. Обратите внимание, что значение, возвращаемое с помощьюpersistent_id()
, само по себе не может иметь постоянного идентификатора.Смотрите Постоянство внешних объектов для получения подробной информации и примеров использования.
- dispatch_table¶
Диспетчерская таблица объекта pickler представляет собой реестр функций сокращения, которые могут быть объявлены с помощью
copyreg.pickle()
. Это отображение, ключами которого являются классы, а значениями - функции сокращения. Функция сокращения принимает единственный аргумент связанного класса и должна соответствовать тому же интерфейсу, что и метод__reduce__()
.По умолчанию у picklerobject не будет атрибута
dispatch_table
, и вместо этого он будет использовать глобальную таблицу диспетчеризации, управляемую модулемcopyreg
. Однако, чтобы настроить обработку для конкретного объекта pickler, можно установить атрибутdispatch_table
для объекта, подобного dict. В качестве альтернативы, если подклассPickler
имеет атрибутdispatch_table
, то он будет использоваться в качестве таблицы отправки по умолчанию для экземпляров этого класса.Примеры использования приведены в разделе Таблицы отправки.
Добавлено в версии 3.3.
- reducer_override(obj)¶
Специальный редуктор, который может быть определен в подклассах
Pickler
. Этот метод имеет приоритет перед любым редуктором вdispatch_table
. Он должен соответствовать тому же интерфейсу, что и метод__reduce__()
, и может дополнительно возвращатьNotImplemented
для резервного копирования наdispatch_table
зарегистрированных редукторах для обработкиobj
.Подробный пример приведен в разделе Пользовательское сокращение для типов, функций и других объектов.
Добавлено в версии 3.8.
- fast¶
Осуждаемый. Включите быстрый режим, если установлено значение true. Быстрый режим отключает использование memo, тем самым ускоряя процесс маринования за счет исключения создания лишних введенных опкодов. Его не следует использовать с самореферентными объектами, в противном случае это приведет к бесконечной рекурсии
Pickler
.Используйте
pickletools.optimize()
, если вам нужны более компактные маринованные огурцы.
- class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
Для этого требуется двоичный файл для считывания потока данных pickle.
Версия протокола рассола определяется автоматически, поэтому никаких аргументов протокола не требуется.
У аргумента file должно быть три метода: метод read(), который принимает целочисленный аргумент, метод readinto(), который принимает аргумент buffer, и метод readline(), который не требует аргументов, как в интерфейсе
io.BufferedIOBase
. Таким образом, file может быть файлом на диске, открытым для двоичного чтения, объектомio.BytesIO
или любым другим пользовательским объектом, который соответствует этому интерфейсу.Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости для pickle stream, генерируемого Python 2. Если значение fix_imports равно true, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. encoding и errors указывают pickle, как декодировать 8-разрядные экземпляры string, выбранные Python 2; по умолчанию они имеют значения „ASCII“ и „strict“ соответственно. Кодировка может быть „bytes“, чтобы считывать эти 8-разрядные экземпляры string как объекты bytes. Использование
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 объекта Picklerobject.
Изменено в версии 3.8: Был добавлен аргумент buffers.
- load()¶
Считывает отобранное представление объекта из открытого файлового объекта, указанного в конструкторе, и возвращает указанную в нем иерархию восстановленных объектов. Байты после отобранного представления объекта игнорируются.
- persistent_load(pid)¶
По умолчанию задается значение
UnpicklingError
.Если задан,
persistent_load()
должен возвращать объект, указанный с помощью постоянного идентификатора pid. Если обнаружен недопустимый постоянный идентификатор, следует ввестиUnpicklingError
.Смотрите Постоянство внешних объектов для получения подробной информации и примеров использования.
- find_class(module, name)¶
При необходимости импортируйте module и верните из него объект с именем name, где аргументами module и 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
(байты без знака).BufferError
вызывается, если буфер не является ни C-, ни Fortran-смежным.
- release()¶
Освободите базовый буфер, предоставляемый объектом PickleBuffer.
Что можно мариновать и не мариновать?¶
Можно мариновать следующие виды рыбы:
встроенные константы (
None
,True
,False
,Ellipsis
, иNotImplemented
);целые числа, числа с плавающей запятой, комплексные числа;
строки, байты, bytearrays;
кортежи, списки, наборы и словари, содержащие только объекты с возможностью выбора;
функции (встроенные и определяемые пользователем), доступные с верхнего уровня модуля (с использованием
def
, а неlambda
);классы, доступные с верхнего уровня модуля;
экземпляры таких классов, результат вызова которых
__getstate__()
доступен для выбора (подробности смотрите в разделе Экземпляры класса маринования).
Попытки выделить недоступные объекты вызовут исключение PicklingError
; когда это произойдет, возможно, в базовый файл уже было записано неопределенное количество байт. Попытка обработать сильно рекурсивную структуру данных может привести к превышению максимальной глубины рекурсии, в этом случае будет задано значение RecursionError
. Вы можете осторожно увеличить это ограничение с помощью sys.setrecursionlimit()
.
Обратите внимание, что функции (встроенные и определяемые пользователем) выбираются полностью по qualified name, а не по значению. [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 в протоколах 2 и 3 вместо
__getnewargs_ex__()
вызывался__getnewargs__()
.
- 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__()
, он вызывается с состоянием unpickled. В этом случае нет необходимости, чтобы объект state был словарем. В противном случае выбранное состояние должно быть словарем, и его элементы назначаются словарю нового экземпляра.Примечание
Если
__reduce__()
возвращает состояние со значениемNone
при извлечении, метод__setstate__()
не будет вызван при извлечении.
Обратитесь к разделу Обработка объектов с отслеживанием состояния для получения дополнительной информации о том, как использовать методы __getstate__()
и __setstate__()
.
Примечание
Во время распаковки экземпляра могут быть вызваны некоторые методы, такие как __getattr__()
, __getattribute__()
, или __setattr__()
. В случае, если эти методы полагаются на то, что какой-то внутренний инвариант является истинным, тип должен реализовывать __new__()
, чтобы установить такой инвариант, поскольку __init__()
не вызывается при распаковке экземпляра.
Как мы увидим, pickle напрямую не использует методы, описанные выше. На самом деле, эти методы являются частью протокола копирования, который реализует специальный метод __reduce__()
. Протокол копирования предоставляет унифицированный интерфейс для получения данных, необходимых для обработки и копирования объектов. [4]
Несмотря на свою эффективность, реализация __reduce__()
непосредственно в ваших классах чревата ошибками. По этой причине разработчикам классов следует по возможности использовать высокоуровневый интерфейс (т.е., __getnewargs_ex__()
, __getstate__()
и __setstate__()
). Однако мы покажем случаи, когда использование __reduce__()
является единственным вариантом или приводит к более эффективному маринованию, или и к тому, и к другому.
- object.__reduce__()¶
В настоящее время интерфейс определен следующим образом. Метод
__reduce__()
не принимает аргументов и должен возвращать либо строку, либо, предпочтительно, кортеж (возвращаемый объект часто называют «уменьшающим значением»).Если возвращается строка, то ее следует интерпретировать как имя глобальной переменной. Это должно быть локальное имя объекта относительно его модуля; модуль pickle выполняет поиск в пространстве имен module для определения модуля объекта. Такое поведение обычно полезно для одиночных объектов.
Возвращаемый кортеж должен содержать от двух до шести элементов. Необязательные элементы могут быть либо опущены, либо в качестве их значения может быть указано
None
. Семантика каждого элемента приведена в порядок:Вызываемый объект, который будет вызван для создания начальной версии объекта.
Набор аргументов для вызываемого объекта. Если вызываемый объект не принимает никаких аргументов, необходимо указать пустой набор аргументов.
Необязательно, состояние объекта, которое будет передано методу объекта
__setstate__()
, как описано ранее. Если у объекта нет такого метода, то значение должно быть словарем и оно будет добавлено к атрибуту объекта__dict__
.Необязательно, итератор (а не последовательность), который выдает последовательные элементы. Эти элементы будут добавлены к объекту либо с помощью
obj.append(item)
, либо, в пакетном режиме, с помощьюobj.extend(list_of_items)
. Это в основном используется для подклассов list, но может использоваться и другими классами, если у них есть append and extend methods с соответствующей подписью. (Будет ли использоватьсяappend()
илиextend()
, зависит от того, какая версия протокола pickle используется, а также от количества добавляемых элементов, поэтому должны поддерживаться оба варианта.)Необязательно, итератор (не последовательность), выдающий последовательные пары ключ-значение. Эти элементы будут сохранены в объекте с помощью
obj[key] = value
. Это в основном используется для подклассов словарей, но может использоваться и другими классами, если они реализуют__setitem__()
.Необязательно, вызываемый объект с сигнатурой
(obj, state)
. Этот вызываемый объект позволяет пользователю программно управлять поведением по обновлению состояния определенного объекта вместо использования статического методаobj
. Если__setstate__()
нетNone
, то этот вызываемый объект будет иметь приоритет передobj
-м__setstate__()
.Добавлено в версии 3.8: Был добавлен необязательный шестой элемент кортежа,
(obj, state)
.
- object.__reduce_ex__(protocol)¶
В качестве альтернативы может быть определен метод
__reduce_ex__()
. Единственное отличие заключается в том, что этот метод должен принимать единственный целочисленный аргумент - версию протокола. Когда он будет определен, pickle предпочтет его методу__reduce__()
. Кроме того,__reduce__()
автоматически становится синонимом расширенной версии. Основное назначение этого метода - обеспечить обратную совместимость значений reduce для более старых версий Python.
Постоянство внешних объектов¶
В интересах сохранения объекта модуль pickle
поддерживает понятие ссылки на объект вне выбранного потока данных. На такие объекты ссылается постоянный идентификатор, который должен быть либо строкой буквенно-цифровых символов (для протокола 0) [5], либо просто произвольным объектом (для любого более нового протокола).
Разрешение таких постоянных идентификаторов не определено модулем pickle
; он делегирует это разрешение пользовательским методам в pickler и unpickler, 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!'
Пользовательское сокращение для типов, функций и других объектов¶
Добавлено в версии 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, не имеют этого ограничения и позволяют использовать выборку с нулевым количеством копий (или создавать как можно меньше копий) при передаче между различными процессами или системами.
См.также
PEP 574 – Протокол Pickle 5 для передачи внеполосных данных
Ограничение глобальных параметров¶
По умолчанию unpickling импортирует любой класс или функцию, которые он находит в данных pickle. Для многих приложений такое поведение неприемлемо, поскольку оно позволяет unpickler импортировать и вызывать произвольный код. Просто подумайте, что делает этот поток данных 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
Высокопроизводительная сериализация встроенных типов.
Сноски