pickle — Сериализация объектов Python
Исходный код: Lib/pickle.py
Модуль pickle реализует двоичные протоколы для сериализации и десериализации структуры Python-объектов. «Заморозка» — процесс преобразования иерархии Python-объектов в поток байтов, а «разморозка» — обратный процесс, при котором поток байтов (из двоичного файла или объекта типа байты) преобразуется обратно в иерархию объектов. Заморозка (и разморозка) также известна как «сериализация», «маршаллинг», 1 или «распаковку»; однако, чтобы избежать путаницы, в данном контексте используются термины «заморозка» и «разморозка».
Предупреждение
Модуль pickle небезопасен. Размораживайте только данные, которым вы доверяете.
Существует возможность создания вредоносных данных pickle, которые во время разморозки выполнят произвольный код. Никогда не размораживайте данные, которые могут поступать из недоверенного источника или быть изменёнными.
Рассмотрите возможность подписи данных с помощью hmac, если вам нужно убедиться, что данные не были изменены.
Для обработки недоверенных данных могут быть более подходящими более безопасные форматы сериализации, такие как json. См. Сравнение с json.
Связь с другими модулями Python
Сравнение с marshal
В Python существует более примитивный модуль сериализации под названием marshal, но в общем случае pickle всегда должен быть предпочтительным способом сериализации Python-объектов. marshal существует в первую очередь для поддержки Python .pyc файлов.
Модуль pickle отличается от marshal несколькими существенными способами:
-
Модуль
pickleотслеживает объекты, которые он уже сериализовал, поэтому последующие ссылки на тот же объект не будут сериализованы повторно.marshalэтого не делает.Это имеет последствия как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты — это объекты, содержащие ссылки на самих себя. Они не обрабатываются marshal, и, по сути, попытка маршаллить рекурсивные объекты приведёт к аварийному завершению интерпретатора Python. Совместное использование объектов происходит, когда на один и тот же объект в разных частях сериализуемой иерархии объектов есть несколько ссылок.
pickleхранит такие объекты только один раз и гарантирует, что все остальные ссылки указывают на основную копию. Общие объекты остаются общими, что может быть очень важно для изменяемых объектов. -
marshalнельзя использовать для сериализации пользовательских классов и их экземпляров.pickleможет сохранять и восстанавливать экземпляры классов прозрачно, но определение класса должно быть импортируемым и находиться в том же модуле, что и при хранении объекта. - Формат сериализации
marshalне гарантируется как переносимый между версиями Python. Поскольку его основная задача — поддержка.pycфайлов, разработчики Python оставляют за собой право изменять формат сериализации несовместимым образом, если возникнет такая необходимость. Формат сериализацииpickleгарантированно совместим между версиями Python, при условии выбора совместимого протокола pickle и обработки кодом сериализации и десериализации различий типов Python 2 и Python 3, если ваши данные пересекают эту уникальную границу разрыва совместимости языков.
Сравнение с json
Между протоколами pickle и JSON (JavaScript Object Notation) существуют фундаментальные различия:
- JSON — это текстовый формат сериализации (он выводит текст Unicode, хотя в большинстве случаев он затем кодируется в
utf-8), а pickle — двоичный; - JSON — удобочитаемый, а pickle — нет;
- JSON — взаимозаменяемый и широко используется вне экосистемы Python, а pickle — специфичен для Python;
- JSON по умолчанию может представлять только подмножество встроенных типов Python и никаких пользовательских классов; pickle может представлять чрезвычайно большое количество типов Python (многие из них автоматически, благодаря использованию возможностей интроспекции Python; сложные случаи могут быть решены путем реализации специфических API объектов);
- В отличие от pickle, десериализация недоверенного JSON сама по себе не создаёт уязвимости для выполнения произвольного кода.
См. также
Модуль json: модуль стандартной библиотеки, позволяющий сериализацию и десериализацию JSON.
Формат потока данных
Формат данных, используемый pickle, специфичен для Python. Это даёт преимущество отсутствия ограничений, накладываемых внешними стандартами, такими как JSON или XDR (которые не могут представлять совместное использование указателей); однако это означает, что программы, не написанные на Python, могут не быть способны восстановить закодированные Python объекты.
По умолчанию, формат данных pickle использует относительно компактное двоичное представление. Если вам необходимы оптимальные характеристики размера, вы можете эффективно сжать закодированные данные.
Модуль pickletools содержит инструменты для анализа потоков данных, генерируемых pickle. Исходный код pickletools содержит обширные комментарии об операционных кодах, используемых протоколами pickle.
В настоящее время доступны 6 различных протоколов, которые могут быть использованы для кодирования. Чем выше используемый протокол, тем более поздняя версия Python требуется для чтения сгенерированного pickle.
- Протокол версии 0 — это оригинальный «человекочитаемый» протокол и обратно совместим с более ранними версиями Python.
- Протокол версии 1 — это старый двоичный формат, также совместимый с более ранними версиями Python.
- Протокол версии 2 был представлен в Python 2.3. Он обеспечивает гораздо более эффективное кодирование классов нового стиля. Обратитесь к 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 -
Целое число, наивысшая доступная версия протокола. Это значение может быть передано в качестве значения протокола функциям
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 определяется автоматически, поэтому аргумент 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.
Версия протокола 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) -
Это принимает двоичный файл для записи потока данных пикла.
Необязательный аргумент protocol, целое число, сообщает пиклеру использовать указанный протокол; поддерживаемые протоколы — от 0 до
HIGHEST_PROTOCOL. Если не указано, по умолчанию используетсяDEFAULT_PROTOCOL. Если указано отрицательное число, выбираетсяHIGHEST_PROTOCOL.Аргумент file должен иметь метод write(), принимающий единственный байтовый аргумент. Таким образом, это может быть файл на диске, открытый для двоичного записи, экземпляр
io.BytesIOили любой другой пользовательский объект, соответствующий этому интерфейсу.Если fix_imports истинно, а protocol меньше 3, пикл попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных пикла был читаем с Python 2.
Если buffer_callback равно None (по умолчанию), представления буферов сериализуются в file в качестве части потока пикла.
Если buffer_callback не равно None, то оно может вызываться любое количество раз с представлением буфера. Если обратный вызов возвращает ложное значение (например, None), данный буфер вне потока; в противном случае буфер сериализуется внутри потока, т.е. внутри потока пикла.
Ошибка, если buffer_callback не равно None, а protocol равно None или меньше 5.
Изменено в версии 3.8: Аргумент buffer_callback был добавлен.
-
dump(obj) -
Записать сериализованное представление obj в открытый объект файла, заданный в конструкторе.
-
persistent_id(obj) -
По умолчанию ничего не делает. Это существует, чтобы дочерний класс мог переопределить его.
Если
persistent_id()возвращаетNone, obj сериализуется как обычно. Любое другое значение заставляетPicklerвыводить возвращенное значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно быть определеноUnpickler.persistent_load(). Обратите внимание, что возвращаемое значениеpersistent_id()само не может иметь постоянный идентификатор.См. Сохранение внешних объектов для получения подробностей и примеров использования.
-
dispatch_table -
Таблица диспетчеризации объекта пиклера — это реестр функций сокращения, которые можно объявить с помощью
copyreg.pickle(). Это отображение, ключи которого — классы, а значения — функции сокращения. Функция сокращения принимает один аргумент соответствующего класса и должна соответствовать тому же интерфейсу, что и метод__reduce__().По умолчанию у объекта пиклера не будет атрибута
dispatch_table, и он вместо этого будет использовать глобальную таблицу диспетчеризации, управляемую модулемcopyreg. Однако для настройки сериализации для конкретного объекта пиклера можно установить атрибутdispatch_tableна объект типа dict. Кроме того, если у подклассаPicklerесть атрибутdispatch_table, то он будет использоваться в качестве таблицы диспетчеризации по умолчанию для экземпляров этого класса.См. Таблицы диспетчеризации для примеров использования.
Новое в версии 3.3.
-
reducer_override(obj) -
Специальный редьюсер, который может быть определен в подклассах
Pickler. Этот метод имеет приоритет над любым редьюсером вdispatch_table. Он должен соответствовать тому же интерфейсу, что и метод__reduce__(), и может необязательно возвращатьNotImplemented, чтобы перейти к редьюсерам, зарегистрированным вdispatch_table, для сериализацииobj.Для подробного примера см. Настройка сокращения для типов, функций и других объектов.
Новое в версии 3.8.
-
fast -
Устарело. Включить быстрый режим, если значение истинно. Быстрый режим отключает использование memo, поэтому ускоряет процесс сериализации, не генерируя избыточные коды PUT. Его нельзя использовать с объектами, ссылающимися на себя, так как в противном случае
Picklerбудет рекурсировать бесконечно.Используйте
pickletools.optimize(), если вам нужны более компактные pickles.
-
-
class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Это принимает двоичный файл для чтения потока данных pickle.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не нужен.
Аргумент file должен иметь три метода: метод read() с целочисленным аргументом, метод readinto() с аргументом буфера и метод readline() без аргументов, как в интерфейсе
io.BufferedIOBase. Таким образом, file может быть файлом на диске, открытым для двоичного чтения, объектомio.BytesIOили любым другим пользовательским объектом, который соответствует этому интерфейсу.Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости для потока pickle, сгенерированного Python 2. Если fix_imports имеет значение True, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. encoding и errors сообщают pickle, как декодировать 8-битные строковые экземпляры, закодированные Python 2; они по умолчанию равны ‘ASCII’ и ‘strict’ соответственно. encoding может быть ‘bytes’, чтобы читать эти 8-битные строковые экземпляры как объекты bytes. Для распаковки массивов NumPy и экземпляров
datetime,dateиtime, закодированных Python 2, необходимо использоватьencoding='latin1'.Если buffers равно None (значение по умолчанию), то все данные, необходимые для десериализации, должны быть содержаться в потоке pickle. Это означает, что аргумент buffer_callback был None при создании
Pickler(или при вызовеdump()илиdumps()).Если buffers не равно None, оно должно быть итерируемым объектом с поддержкой буферов, который используется каждый раз, когда поток pickle ссылается на внешний буфер. Такие буферы были предоставлены в порядке аргументу buffer_callback объекта Pickler.
Изменено в версии 3.8: Добавлен аргумент buffers.
-
load() -
Прочитайте закодированное представление объекта из открытого файла, указанного в конструкторе, и верните реконструированную иерархию объектов, указанную в нём. Байты после закодированного представления объекта игнорируются.
-
persistent_load(pid) -
По умолчанию вызывает
UnpicklingError.Если определено,
persistent_load()должно возвращать объект, указанный постоянным идентификатором pid. Если встречен неверный постоянный идентификатор, должно быть вызвано исключениеUnpicklingError.Подробные сведения и примеры использования см. в разделе Сохранение внешних объектов.
-
find_class(module, name) -
Импортировать module, если необходимо, и вернуть объект, названный name из него, где аргументы module и name являются объектами
str. Обратите внимание, что, вопреки своему названию,find_class()также используется для поиска функций.Подклассы могут переопределять его, чтобы получить контроль над типами объектов и способами их загрузки, потенциально снижая риски безопасности. Подробности см. в разделе Ограничение глобальных переменных.
Вызывает событие аудита
pickle.find_classс аргументамиmodule,name.
-
-
class pickle.PickleBuffer(buffer) -
Обёртка для буфера, представляющего закодируемые данные. buffer должен быть объектом, предоставляющим буфер, например, байтовый объект или N-мерный массив.
PickleBufferсам по себе является поставщиком буфера, поэтому его можно передать другим API, ожидающим объект, предоставляющий буфер, например,memoryview.Объекты
PickleBufferмогут быть сериализованы только с помощью протокола pickle 5 или выше. Они подходят для сериализации вне области.Новое в версии 3.8.
-
raw() -
Возвращает
memoryviewобласти памяти, лежащей в основе этого буфера. Возвращаемый объект является одномерным, C-непрерывным memoryview с форматом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 restore(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
Классы могут изменить поведение по умолчанию, предоставив один или несколько специальных методов:
-
object.__getnewargs_ex__() -
В протоколах 2 и выше, классы, реализующие метод
__getnewargs_ex__(), могут диктовать значения, передаваемые методу__new__()при десериализации. Метод должен вернуть пару(args, kwargs)где args — кортеж позиционных аргументов, а kwargs — словарь именованных аргументов для построения объекта. Эти значения будут переданы методу__new__()при десериализации.Этот метод следует реализовать, если методу
__new__()вашего класса требуются только именованные аргументы. В противном случае, для обеспечения совместимости рекомендуется реализовать метод__getnewargs__().Изменено в версии 3.6:
__getnewargs_ex__()теперь используется в протоколах 2 и 3.
-
object.__getnewargs__() -
Этот метод выполняет аналогичную функцию, что и
__getnewargs_ex__(), но поддерживает только позиционные аргументы. Он должен вернуть кортеж аргументовargsкоторые будут переданы методу__new__()при десериализации.Метод
__getnewargs__()не будет вызван, если определён метод__getnewargs_ex__().Изменено в версии 3.6: До Python 3.6,
__getnewargs__()вызывался вместо__getnewargs_ex__()в протоколах 2 и 3.
-
object.__getstate__() -
Классы могут дополнительно влиять на то, как сериализуются их экземпляры; если класс определяет метод
__getstate__(), он вызывается, и возвращаемый объект сериализуется как содержимое экземпляра, вместо содержимого словаря экземпляра. Если метод__getstate__()отсутствует, словарь экземпляра__dict__сериализуется обычным образом.
-
object.__setstate__(state) -
При десериализации, если класс определяет
__setstate__(), он вызывается со состоянием, десериализованным из данных. В этом случае нет требования, чтобы объект состояния был словарем. В противном случае сохранённое состояние должно быть словарем, и его элементы присваиваются словарю нового экземпляра.Примечание
Если
__getstate__()возвращает ложное значение, метод__setstate__()не будет вызван при десериализации.
Дополнительную информацию о том, как использовать методы __getstate__() и __setstate__(), см. в разделе Обработка объектов состояния.
Примечание
При десериализации некоторые методы, такие как __getattr__(), __getattribute__(), или __setattr__(), могут быть вызваны у экземпляра. В случае, если эти методы полагаются на то, что некоторое внутреннее инвариантное свойство истинно, тип должен реализовать __new__(), чтобы установить этот инвариант, так как __init__() не вызывается при десериализации экземпляра.
Как мы увидим, pickle напрямую не использует описанные выше методы. На самом деле, эти методы являются частью протокола копирования, который реализует специальный метод __reduce__(). Протокол копирования предоставляет унифицированный интерфейс для извлечения данных, необходимых для сериализации и копирования объектов.
Несмотря на мощь, непосредственная реализация __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). Этот вызываемый объект позволяет пользователю программно контролировать поведение обновления состояния конкретного объекта вместо использования статического методаobjобъекта. Если неNone, этот вызываемый объект будет иметь приоритет над методом__setstate__()объекта.Новое в версии 3.8: Дополнительный шестой элемент кортежа,
(obj, state), был добавлен.
-
object.__reduce_ex__(protocol) -
В качестве альтернативы, может быть определён метод
__reduce_ex__(). Единственное отличие состоит в том, что этот метод должен принимать один целочисленный аргумент — версию протокола. Если он определён, pickle будет отдавать предпочтение ему методу__reduce__(). Кроме того, метод__reduce__()автоматически становится синонимом расширенной версии. Основное использование этого метода заключается в обеспечении обратной совместимости значений уменьшения для более старых версий Python.
Персистентность внешних объектов
Для обеспечения персистентности объектов модуль pickle поддерживает понятие ссылки на объект, находящийся вне потока сериализованных данных. Такие объекты ссылаются на них с помощью персистентного идентификатора, который должен быть либо строкой из алфавитно-цифровых символов (для протокола 0) 5, либо произвольным объектом (для любого более позднего протокола).
Разрешение таких персистентных идентификаторов не определяется модулем pickle; он делегирует это разрешение пользовательским методам сериализатора и десериализатора, persistent_id() и persistent_load() соответственно.
Для сериализации объектов с внешним персистентным идентификатором сериализатор должен иметь пользовательский метод persistent_id(), который принимает объект в качестве аргумента и возвращает либо None, либо персистентный идентификатор для этого объекта. Когда возвращается None, сериализатор просто сериализует объект обычным способом. Когда возвращается строковый персистентный идентификатор, сериализатор сериализует этот объект вместе с маркером, чтобы десериализатор распознал его как персистентный идентификатор.
Для десериализации внешних объектов десериализатор должен иметь пользовательский метод persistent_load(), который принимает объект персистентного идентификатора и возвращает ссылаемый объект.
Вот пример, демонстрирующий, как персистентный идентификатор может быть использован для сериализации внешних объектов по ссылке.
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
Таблицы перенаправления
Если необходимо настроить сериализацию некоторых классов, не затрагивая код, зависящий от сериализации, можно создать сериализатор с собственной таблицей перенаправления.
Глобальная таблица перенаправления, управляемая модулем copyreg, доступна как copyreg.dispatch_table. Поэтому можно использовать модифицированную копию copyreg.dispatch_table в качестве собственной таблицы перенаправления.
Например
f = io.BytesIO() p = pickle.Pickler(f) p.dispatch_table = copyreg.dispatch_table.copy() p.dispatch_table[SomeClass] = reduce_SomeClass
создаёт экземпляр pickle.Pickler с собственной таблицей перенаправления, которая обрабатывает класс SomeClass особым образом. Альтернативно, код
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
делает то же самое, но все экземпляры MyPickler по умолчанию используют общую собственную таблицу перенаправления. С другой стороны, код
copyreg.pickle(SomeClass, reduce_SomeClass) f = io.BytesIO() p = pickle.Pickler(f)
модифицирует глобальную таблицу перенаправления, используемую всеми пользователями модуля copyreg.
Обработка состояний объектов
Вот пример, показывающий, как изменить поведение сериализации для класса. Класс TextReader открывает текстовый файл и возвращает номер строки и содержимое строки каждый раз, когда вызывается его метод readline(). Если экземпляр TextReader сериализуется, все атрибуты, *кроме* члена объекта файла, сохраняются. При десериализации экземпляра файл повторно открывается, и чтение возобновляется с последнего местоположения. Методы __setstate__() и __getstate__() используются для реализации этого поведения.
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
self.file = file
Пример использования может выглядеть так:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
Настройка сокращений для типов, функций и других объектов
Новое в версии 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 с данными вне зоны обмена
Ограничение глобальных переменных
По умолчанию разсериализация импортирует любой класс или функцию, найденную в данных сериализации. Для многих приложений это поведение неприемлемо, поскольку оно позволяет разсериализатору импортировать и вызывать произвольный код. Рассмотрим, что делает этот специально созданный поток данных сериализации при загрузке:
>>> 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)
В следующем примере читаются данные, закодированные с помощью pickle.
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
См. также
-
Modulecopyreg -
Регистрация конструкторов интерфейса pickle для типов расширений.
-
Modulepickletools -
Инструменты для работы с закодированными данными и их анализа.
-
Moduleshelve -
Индексированные базы данных объектов; использует
pickle. -
Modulecopy -
Копирование объектов поверхностно и глубоко.
-
Modulemarshal -
Высокопроизводительная сериализация встроенных типов.
Примечания
-
1 -
Не путайте это с модулем
marshal -
2 -
Вот почему функции
lambdaнельзя закодировать с помощью pickle: все функцииlambdaимеют одно и то же имя:<lambda>. -
3 -
Возникающее исключение, скорее всего, будет
ImportErrorилиAttributeError, но это может быть что-то другое. -
4 -
Модуль
copyиспользует этот протокол для операций копирования поверхностно и глубоко. -
5 -
Ограничение на символы алфавитно-цифрового типа вызвано тем, что идентификаторы в протоколе 0 разделяются символом новой строки. Поэтому если в идентификаторах присутствуют символы новой строки, то полученные закодированные данные станут нечитаемыми.
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/library/pickle.html