Содержание страницы
pickle – сериализация объектов Python¶pickle – Python object serialization
Исходный код: Lib/pickle.py
Модуль pickle реализует двоичные протоколы для сериализации и
de-сериализации структуры объектов Python. «Упаковка» – это процесс
преобразования иерархии объектов Python в поток байтов, а
«распаковка» – обратная операция, при которой поток байтов
(из двоичного файла или байтоподобного объекта) преобразуется
обратно в иерархию объектов. Упаковка (и распаковка) также известна
как «сериализация», «маршалинг», [1] или «уплощение»; однако, чтобы
избежать путаницы, здесь используются термины «упаковка» и «распаковка».
Предупреждение
Модуль pickle небезопасен. Распаковывайте только те данные, которым доверяете.
Существует возможность создать вредоносные данные pickle, которые выполнят произвольный код во время распаковки. Никогда не распаковывайте данные, которые могли поступить из ненадёжного источника или могли быть изменены.
Если необходимо гарантировать, что данные не были изменены, рассмотрите их подписание с помощью hmac.
Более безопасные форматы сериализации, такие как json, могут быть более подходящими, если вы обрабатываете ненадёжные данные. См. Сравнение с json.
Отношение к другим модулям Python¶Relationship to other Python modules
Сравнение с marshal¶Comparison with marshal
В Python есть более примитивный модуль сериализации marshal, но в целом pickle всегда должен быть предпочтительным способом сериализации объектов Python. marshal существует в первую очередь для поддержки файлов .pyc в Python.
Модуль pickle отличается от marshal несколькими важными особенностями:
marshalнельзя использовать для сериализации пользовательских классов и их экземпляров.pickleможет прозрачно сохранять и восстанавливать экземпляры классов, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при упаковке объекта.Формат сериализации
marshalне гарантирует переносимость между версиями Python. Поскольку его основная задача – поддержка файлов.pyc, разработчики Python оставляют за собой право изменять формат сериализации несовместимым с предыдущими версиями образом, если возникнет необходимость. Формат сериализацииpickleгарантированно обратно совместим между выпусками Python при условии выбора совместимого протокола pickle и обработки различий типов между Python 2 и Python 3 в коде упаковки и распаковки, если ваши данные пересекают эту уникальную границу изменения языка.
Сравнение с json¶Comparison with json
Существуют принципиальные различия между протоколами pickle и JSON (JavaScript Object Notation):
JSON – это текстовый формат сериализации (он выводит текст в юникоде, хотя чаще всего затем кодируется в
utf-8), тогда как pickle – двоичный формат сериализации;JSON удобочитаем, а pickle – нет;
JSON интероперабелен и широко используется за пределами экосистемы Python, тогда как pickle является специфичным для Python;
JSON по умолчанию может представлять только подмножество встроенных типов Python и никаких пользовательских классов; pickle может представлять огромное количество типов Python (многие из них автоматически, благодаря умелому использованию возможностей интроспекции Python; сложные случаи можно решить, реализовав специфические объектные API);
В отличие от pickle, десериализация ненадёжного JSON сама по себе не создаёт уязвимости для выполнения произвольного кода.
См. также
Модуль json: модуль стандартной библиотеки, обеспечивающий сериализацию и десериализацию JSON.
Формат потока данных¶Data stream format
Формат данных, используемый pickle, является специфичным для Python. Это даёт преимущество: отсутствуют ограничения, накладываемые внешними стандартами, такими как JSON (который не может представлять совместное использование указателей); однако это означает, что программы на других языках могут не смочь восстановить упакованные объекты 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–3.13. Обратитесь к PEP 3154 для получения информации об улучшениях, привнесённых протоколом 4.
Протокол версии 5 добавлен в Python 3.8. Он поддерживает внеполосные данные и ускоряет работу с внутриполосными данными. Начиная с Python 3.14, это протокол по умолчанию. Информацию об улучшениях, внесённых протоколом 5, см. в PEP 574.
Примечание
Сериализация – это более примитивное понятие, чем сохранение (persistence); хотя pickle читает и записывает файловые объекты, он не решает ни проблему именования сохраняемых объектов, ни (ещё более сложную) проблему параллельного доступа к сохраняемым объектам. Модуль pickle может преобразовать сложный объект в поток байтов и обратно – в объект с той же внутренней структурой. Возможно, самое очевидное применение этих потоков байтов – запись в файл, но также можно передавать их по сети или хранить в базе данных. Модуль shelve предоставляет простой интерфейс для упаковки (pickle) и распаковки (unpickle) объектов в файлах баз данных в стиле DBM.
Интерфейс модуля¶Module Interface
Чтобы сериализовать иерархию объектов, достаточно вызвать функцию dumps(). Аналогично, для десериализации потока данных вызывается функция loads(). Однако если требуется более тонкий контроль над сериализацией и десериализацией, можно создать объект Pickler или Unpickler соответственно.
Модуль pickle предоставляет следующие константы:
- pickle.HIGHEST_PROTOCOL¶
Целое число – наивысшая доступная версия протокола. Это значение можно передавать в качестве аргумента протокола функциям
dump()иdumps(), а также конструкторуPickler.
- pickle.DEFAULT_PROTOCOL¶
Целое число – версия протокола по умолчанию, используемая для упаковки. Может быть меньше
HIGHEST_PROTOCOL. В настоящее время протокол по умолчанию – 5, появившийся в Python 3.8 и несовместимый с предыдущими версиями. Эта версия добавляет поддержку внеполосных буферов, благодаря чему данные, совместимые с PEP 3118, могут передаваться отдельно от основного потока pickle.Изменено в версии 3.0: Протокол по умолчанию – 3.
Изменено в версии 3.8: Протокол по умолчанию – 4.
Изменено в версии 3.14: Протокол по умолчанию – 5.
Модуль pickle предоставляет следующие функции для упрощения процесса упаковки:
- pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)¶
Записывает упакованное представление объекта obj в открытый файловый объект file. Это эквивалентно
Pickler(file, protocol).dump(obj).Аргументы file, protocol, fix_imports и buffer_callback имеют тот же смысл, что и в конструкторе
Pickler.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
- pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)¶
Возвращает упакованное представление объекта obj в виде объекта
bytes, а не записывает его в файл.Аргументы protocol, fix_imports и buffer_callback имеют тот же смысл, что и в конструкторе
Pickler.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
- pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
Читает упакованное представление объекта из открытого файлового объекта file и возвращает восстановленную иерархию объектов, заданную в нём. Это эквивалентно
Unpickler(file).load().Версия протокола pickle определяется автоматически, поэтому аргумент протокол не требуется. Байты после упакованного представления объекта игнорируются.
Аргументы file, fix_imports, encoding, errors, strict и buffers имеют тот же смысл, что и в конструкторе
Unpickler.Изменено в версии 3.8: Добавлен аргумент buffers.
- pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
Возвращает восстановленную иерархию объектов упакованного представления data объекта. data должен быть байтоподобным объектом.
Версия протокола pickle определяется автоматически, поэтому аргумент протокол не требуется. Байты после упакованного представления объекта игнорируются.
Аргументы fix_imports, encoding, errors, strict и buffers имеют тот же смысл, что и в конструкторе
Unpickler.Изменено в версии 3.8: Добавлен аргумент buffers.
Модуль pickle определяет три исключения:
- exception pickle.PickleError¶
Общий базовый класс для остальных исключений pickle. Он наследуется от
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.
Необязательный аргумент протокол – целое число, указывает упаковщику использовать заданный протокол; поддерживаются протоколы от 0 до
HIGHEST_PROTOCOL. Если аргумент не указан, по умолчанию используетсяDEFAULT_PROTOCOL. Если указано отрицательное число, выбираетсяHIGHEST_PROTOCOL.Аргумент file должен иметь метод write(), принимающий единственный аргумент типа bytes. Таким образом, это может быть файл на диске, открытый для двоичной записи, экземпляр
io.BytesIOили любой другой пользовательский объект, соответствующий этому интерфейсу.Если fix_imports равен true и протокол меньше 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, а протокол равенNoneили меньше 5.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
- dump(obj)¶
Записывает упакованное представление obj в открытый файловый объект, переданный в конструкторе.
- persistent_id(obj)¶
По умолчанию ничего не делает. Этот метод существует, чтобы подкласс мог его переопределить.
Если
persistent_id()возвращаетNone, obj упаковывается как обычно. Любое другое значение заставляетPicklerвыдать возвращённое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно быть определеноUnpickler.persistent_load(). Обратите внимание, что значение, возвращаемоеpersistent_id(), само не может иметь постоянного идентификатора.См. Постоянство внешних объектов для подробностей и примеров использования.
Изменено в версии 3.13: Добавлена реализация этого метода по умолчанию в C-реализации
Pickler.
- dispatch_table¶
Таблица диспетчеризации (dispatch table) объекта Pickler – это реестр функций приведения (reduction functions), которые можно объявить с помощью
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¶
Устарело. Включает быстрый режим, если установлено в истинное значение. Быстрый режим отключает использование мемо, ускоряя процесс упаковки за счёт отсутствия лишних op-кодов PUT. Не следует использовать с самореферентными объектами, иначе
Picklerбудет рекурсивно вызывать себя до бесконечности.Используйте
pickletools.optimize(), если нужны более компактные упаковки.
- clear_memo()¶
Очищает «мемо» упаковщика.
Мемо – это структура данных, которая запоминает, какие объекты упаковщик уже видел, чтобы совместно используемые или рекурсивные объекты упаковывались по ссылке, а не по значению. Этот метод полезен при повторном использовании упаковщиков.
- class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)¶
Принимает бинарный файл для чтения потока данных pickle.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не нужен.
Аргумент file должен иметь три метода: read(), который принимает целочисленный аргумент, readinto(), принимающий аргумент-буфер, и readline(), не требующий аргументов, как в интерфейсе
io.BufferedIOBase. Таким образом, file может быть файлом на диске, открытым для бинарного чтения, объектомio.BytesIOили любым другим пользовательским объектом, соответствующим этому интерфейсу.Необязательные аргументы fix_imports, encoding и errors используются для управления совместимостью с потоком pickle, созданным Python 2. Если fix_imports истинно, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Аргументы encoding и errors сообщают pickle, как декодировать 8-битные строковые экземпляры, упакованные Python 2; по умолчанию они равны 'ASCII' и 'strict' соответственно. encoding может быть 'bytes' для чтения этих 8-битных строк как байтовых объектов. Использование
encoding='latin1'требуется для распаковки массивов NumPy и экземпляровdatetime,dateиtime, упакованных Python 2.Если buffers равен
None(по умолчанию), то все данные, необходимые для десериализации, должны содержаться в потоке pickle. Это означает, что аргумент buffer_callback былNone, когда был создан экземплярPickler(или когда была вызванаdump()илиdumps()).Если buffers не равен
None, это должен быть итерируемый объект, содержащий объекты с поддержкой буфера, который расходуется каждый раз, когда поток pickle ссылается на внеполосное представление буфера. Такие буферы были переданы по порядку в buffer_callback объекта Pickler.Изменено в версии 3.8: Добавлен аргумент buffers.
- load()¶
Читает упакованное представление объекта из открытого файлового объекта, переданного конструктору, и возвращает восстановленную иерархию объектов, заданную в нём. Байты после упакованного представления объекта игнорируются.
- persistent_load(pid)¶
По умолчанию вызывает
UnpicklingError.Если определено,
persistent_load()должен возвращать объект, указанный постоянным идентификатором pid. Если встречен недопустимый постоянный идентификатор, должно быть вызваноUnpicklingError.См. Постоянство внешних объектов для подробностей и примеров использования.
Изменено в версии 3.13: Добавлена реализация этого метода по умолчанию в C-реализации
Unpickler.
- 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.
Какие объекты можно сериализовать и десериализовать?¶What can be pickled and unpickled?
Следующие типы можно сериализовать:
встроенные константы (
None,True,False,EllipsisиNotImplemented);целые числа, числа с плавающей запятой, комплексные числа;
строки, байты, байтовые массивы;
кортежи, списки, множества и словари, содержащие только сериализуемые объекты;
функции (встроенные и определённые пользователем), доступные на верхнем уровне модуля (через
def, а неlambda);классы, доступные на верхнем уровне модуля;
экземпляры таких классов, для которых результат вызова
__getstate__()является сериализуемым (подробнее см. раздел Сериализация экземпляров классов).
Попытки сериализовать несериализуемые объекты приведут к возникновению исключения PicklingError; при этом на диск может быть уже записано неизвестное количество байтов. Попытка сериализовать сильно рекурсивную структуру данных может превысить максимальную глубину рекурсии – в этом случае будет возбуждено RecursionError. Можно аккуратно увеличить этот лимит с помощью sys.setrecursionlimit().
Обратите внимание, что функции (встроенные и определённые пользователем) сериализуются по полностью квалифицированному имени, а не по значению. [2] Это означает, что сериализуется только имя функции, а также имя содержащего модуля и классов. Ни код функции, ни её атрибуты не сериализуются. Поэтому модуль, в котором определена функция, должен быть импортируемым в среде десериализации, и модуль должен содержать указанный объект, иначе будет возбуждено исключение. [3]
Аналогичным образом классы сериализуются по полностью квалифицированному имени, поэтому на среду десериализации накладываются те же ограничения. Обратите внимание, что ни код класса, ни его данные не сериализуются, поэтому в следующем примере атрибут класса attr не восстанавливается в среде десериализации:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
Именно из-за этих ограничений сериализуемые функции и классы должны быть определены на верхнем уровне модуля.
Аналогично, при сериализации экземпляров классов код и данные самого класса не сериализуются вместе с ними. Сериализуются только данные экземпляра. Это сделано намеренно, чтобы можно было исправить ошибки в классе или добавить в него методы и по-прежнему загружать объекты, созданные с помощью более старой версии класса. Если планируется использовать долгоживущие объекты, которые переживут множество версий класса, будет разумно добавить в объекты номер версии, чтобы можно было выполнять соответствующие преобразования с помощью метода __setstate__() класса.
Сериализация экземпляров классов¶Pickling Class Instances
В этом разделе описываются общие механизмы, позволяющие определять, настраивать и контролировать процесс сериализации и десериализации экземпляров классов.
В большинстве случаев для того, чтобы экземпляры стали сериализуемыми, не требуется дополнительного кода. По умолчанию 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__()вашего класса требует только именованных аргументов (keyword-only). В противном случае для обеспечения совместимости рекомендуется реализовать__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__(), он вызывается с распакованным состоянием. В этом случае нет требования, чтобы объект состояния был словарём. В противном случае упакованное состояние должно быть словарём, и его элементы присваиваются словарю нового экземпляра.Примечание
Если
__reduce__()возвращает состояние со значениемNoneпри упаковке, то метод__setstate__()не будет вызван при распаковке.
Обратитесь к разделу Обработка объектов с состоянием за дополнительной информацией о том, как использовать
методы __getstate__() и __setstate__().
Примечание
Во время распаковки для экземпляра могут вызываться некоторые методы, такие как __getattr__(),
__getattribute__() или __setattr__(). Если эти методы полагаются на выполнение некоторого внутреннего инварианта,
тип должен реализовать __new__() для установления такого инварианта,
поскольку __init__() не вызывается при распаковке экземпляра.
Как мы увидим, pickle не использует напрямую описанные выше методы. На
самом деле эти методы являются частью протокола копирования, который реализует
специальный метод __reduce__(). Протокол копирования предоставляет единый
интерфейс для получения данных, необходимых для упаковки и копирования
объектов. [4]
Хотя реализация __reduce__() непосредственно в классах является мощной,
она чревата ошибками. По этой причине разработчикам классов следует
использовать высокоуровневый интерфейс (т.е. __getnewargs_ex__(), __getstate__() и
__setstate__()), когда это возможно. Однако мы покажем случаи, когда
использование __reduce__() является единственным вариантом или приводит
к более эффективной упаковке, или и то и другое.
- object.__reduce__()¶
В настоящее время интерфейс определён следующим образом. Метод
__reduce__()не принимает аргументов и должен возвращать либо строку, либо, желательно, кортеж (возвращаемый объект часто называют «reduce value»).Если возвращается строка, она должна интерпретироваться как имя глобальной переменной. Это должно быть локальное имя объекта относительно его модуля; модуль pickle ищет в пространстве имён модуля, чтобы определить модуль объекта: для данного
obj, подлежащего упаковке, атрибут__module__ищется непосредственно наobj, а если атрибут экземпляра__module__не установлен, происходит поиск по типуobj. Такое поведение обычно полезно для синглтонов (одиночных объектов).Если возвращается кортеж, он должен содержать от двух до шести элементов. Необязательные элементы можно опустить или указать в качестве их значения
None. Семантика каждого элемента следующая (по порядку):Вызываемый объект, который будет вызван для создания начальной версии объекта.
Кортеж аргументов для вызываемого объекта. Если вызываемый объект не принимает ни одного аргумента, должен быть передан пустой кортеж.
Необязательно: состояние объекта, которое будет передано методу
__setstate__()объекта, как описано ранее. Если у объекта нет такого метода, то значение должно быть словарём, и оно будет добавлено в атрибут__dict__объекта.Необязательно: итератор (не последовательность), возвращающий последовательные элементы. Эти элементы будут добавлены к объекту либо с помощью
obj.append(item), либо пакетно с помощьюobj.extend(list_of_items). В основном это используется для подклассов списка, но может использоваться и другими классами, если они имеют методыappend()иextend()с соответствующей сигнатурой. (То, используется лиappend()илиextend(), зависит от используемой версии протокола pickle, а также от количества добавляемых элементов, поэтому должны поддерживаться оба.)Необязательно: итератор (не последовательность), возвращающий последовательные пары ключ-значение. Эти элементы будут сохранены в объекте с помощью
obj[key] = value. В основном это используется для подклассов словаря, но может использоваться и другими классами, если они реализуют__setitem__().Необязательно: вызываемый объект с сигнатурой
(obj, state). Этот вызываемый объект позволяет пользователю программно управлять поведением обновления состояния конкретного объекта, вместо использования статического метода__setstate__()объектаobj. Если не указанNone, этот вызываемый объект будет иметь приоритет над__setstate__()методаobj.Добавлено в версии 3.8: Был добавлен необязательный шестой элемент кортежа,
(obj, state).
- object.__reduce_ex__(protocol)¶
В качестве альтернативы может быть определён метод
__reduce_ex__(). Единственное отличие состоит в том, что этот метод должен принимать один целочисленный аргумент – версию протокола. Если он определён, pickle будет предпочитать его методу__reduce__(). Кроме того,__reduce__()автоматически становится синонимом расширенной версии. Основное назначение этого метода – предоставлять обратно совместимые reduce-значения для старых версий Python.
Персистентность внешних объектов¶Persistence of External Objects
Для обеспечения персистентности объектов модуль pickle поддерживает
понятие ссылки на объект вне упакованного потока данных. Такие объекты
идентифицируются по постоянному идентификатору (persistent ID), который должен быть либо
строкой из буквенно-цифровых символов (для протокола 0), [5] либо просто произвольным объектом (для
любого более нового протокола).
Разрешение таких постоянных идентификаторов не определяется модулем pickle;
он делегирует это разрешение определённым пользователем методам упаковщика и распаковщика,
соответственно persistent_id() и persistent_load().
Чтобы упаковать объекты, имеющие внешний постоянный идентификатор, упаковщик должен иметь
пользовательский метод persistent_id(), который принимает объект в качестве
аргумента и возвращает либо None, либо постоянный идентификатор для этого объекта.
Когда возвращается None, упаковщик просто упаковывает объект как обычно.
Когда возвращается строка постоянного идентификатора, упаковщик упаковывает этот объект
вместе с маркером, чтобы распаковщик распознал его как постоянный идентификатор.
Чтобы распаковать внешние объекты, распаковщик должен иметь пользовательский
метод persistent_load(), который принимает объект постоянного идентификатора и
возвращает ссылочный объект.
Вот исчерпывающий пример, показывающий, как постоянный идентификатор может использоваться для упаковки внешних объектов по ссылке.
# Простой пример, показывающий, как с помощью persistent ID можно упаковать внешние объекты по ссылке.
# внешние объекты по ссылке.
import pickle
import sqlite3
from collections import namedtuple
# Простой класс, представляющий запись в нашей базе данных.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Вместо упаковки MemoRecord как обычного экземпляра класса, мы выводим
# постоянный идентификатор.
if isinstance(obj, MemoRecord):
# Здесь наш постоянный идентификатор – это просто кортеж, содержащий метку и ключ, который ссылается на определённую запись в базе данных.
# ключ, который ссылается на определённую запись в базе данных.
return ("MemoRecord", obj.key)
else:
# Этот метод вызывается при каждом обнаружении постоянного идентификатора.
# требуется упаковать как обычно.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# Извлеките запись из базы данных по ссылке и верните её.
# Всегда возбуждает исключение, если невозможно вернуть правильный объект.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Извлеките указанную запись из базы данных и верните её.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Инициализируйте и наполните базу данных.
# Извлеките записи для упаковки.
# по постоянному идентификатору.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Обновите запись для надёжности.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Извлеките записи, которые требуется упаковать.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Выводит и нумерует строки в текстовом файле.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Обновите запись, для верности.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Удаляет не подлежащие упаковке записи.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
Диспетчерские таблицы¶Dispatch Tables
Если требуется настроить упаковку некоторых классов, не затрагивая другой код, зависящий от упаковки, можно создать упаковщик с частной диспетчерской таблицей.
Глобальная диспетчерская таблица, управляемая модулем 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.
Обработка объектов с состоянием¶Handling Stateful Objects
Вот пример, показывающий, как изменить поведение упаковки (pickling) для класса.
Класс TextReader ниже открывает текстовый файл и возвращает номер строки и
содержимое строки каждый раз, когда вызывается его метод readline(). Если
экземпляр TextReader упаковывается, сохраняются все атрибуты, кроме члена-файлового объекта.
При распаковке экземпляра файл открывается заново и
чтение продолжается с последней позиции. Методы __setstate__() и
__getstate__() используются для реализации этого поведения.
class TextReader:
"""Вывести и пронумеровать строки в текстовом файле."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Восстанавливает состояние ранее открытого файла. Для этого необходимо открыть его заново и читать, пока не будет восстановлено количество строк.
# Наконец, сохраняет файл.
# метод, чтобы избежать изменения исходного состояния.
state = self.__dict__.copy()
# Для любого другого объекта используйте обычную редукцию.
del state['file']
return state
def __setstate__(self, state):
# Восстановить атрибуты экземпляра (например, filename и lineno).
self.__dict__.update(state)
# Получить дескриптор исходного буферного объекта.
# открыть его заново и читать, пока количество строк не восстановится.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Наконец, сохраните файл.
self.file = file
Пример использования может выглядеть так:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
Настраиваемая редукция для типов, функций и других объектов¶Custom Reduction for Types, Functions, and Other Objects
Добавлено в версии 3.8.
Иногда dispatch_table может быть недостаточно гибким.
В частности, может потребоваться настроить упаковку на основе другого критерия,
а не только типа объекта, или настроить упаковку функций и классов.
Для таких случаев можно создать подкласс класса Pickler и
реализовать метод reducer_override(). Этот метод может возвращать
произвольный кортеж редукции (см. __reduce__()). Кроме того, он может возвращать
NotImplemented для возврата к традиционному поведению.
Если определены оба метода dispatch_table и
reducer_override(), то приоритет имеет метод reducer_override().
Примечание
Из соображений производительности reducer_override() может не вызываться
для следующих объектов: None, True, False, а также
для точных экземпляров int, float, bytes, str, dict, set, frozenset, list
и tuple.
Вот простой пример, где разрешается упаковка и восстановление заданного класса:
import io
import pickle
class MyClass:
my_attribute = 1
class MyPickler(pickle.Pickler):
def reducer_override(self, obj):
"""Пользовательский редуктор для MyClass."""
if getattr(obj, "__name__", None) == "MyClass":
return type, (obj.__name__, obj.__bases__,
{'my_attribute': obj.my_attribute})
else:
# Для любого другого объекта используется обычная редукция.
return NotImplemented
f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)
del MyClass
unpickled_class = pickle.loads(f.getvalue())
assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1
Буферы внеполосной передачи¶Out-of-band Buffers
Добавлено в версии 3.8.
В некоторых контекстах модуль pickle используется для передачи больших объёмов
данных. Поэтому может быть важно минимизировать количество копирований памяти,
чтобы сохранить производительность и расход ресурсов. Однако нормальная работа
модуля pickle, преобразующего графоподобную структуру объектов в последовательный поток байтов,
по своей сути включает копирование данных в поток pickle и из него.
Этого ограничения можно избежать, если и поставщик (реализация типов объектов для передачи), и потребитель (реализация системы связи) поддерживают механизмы внеполосной передачи, предоставляемые протоколом pickle версии 5 и выше.
API поставщика¶Provider API
Крупные объекты данных, подлежащие упаковке, должны реализовывать метод __reduce_ex__(),
специализированный для протокола 5 и выше, который возвращает
экземпляр PickleBuffer (вместо, например, объекта bytes) для любых больших данных.
Объект PickleBuffer сигнализирует, что соответствующий буфер
пригоден для внеполосной передачи данных. Эти объекты остаются совместимыми
с обычным использованием модуля pickle. Однако потребители также могут
согласиться сообщить pickle, что они будут обрабатывать эти буферы самостоятельно.
API потребителя¶Consumer API
Система связи может включить настраиваемую обработку объектов PickleBuffer,
создаваемых при сериализации графа объектов.
На стороне отправителя необходимо передать аргумент buffer_callback в
Pickler (или в функцию dump() или dumps()), который будет вызываться
для каждого PickleBuffer, генерируемого при упаковке графа объектов.
Буферы, накопленные buffer_callback, не будут скопированы в поток pickle – будет вставлен только дешёвый маркер.
На стороне получателя необходимо передать аргумент buffers в
Unpickler (или в функцию load() или loads()),
который является итерируемым объектом с буферами, переданными в buffer_callback.
Этот итерируемый объект должен выдавать буферы в том же порядке, в котором они были переданы
в buffer_callback. Эти буферы предоставят данные, ожидаемые
восстановителями объектов, чья упаковка породила исходные объекты PickleBuffer.
Между отправляющей и принимающей сторонами система связи может реализовать собственный механизм передачи для внеполосных буферов. Потенциальные оптимизации включают использование общей памяти или сжатия, зависящего от типа данных.
Пример¶Example
Вот тривиальный пример, где мы реализуем подкласс bytearray,
способный участвовать во внеполосной упаковке буферов:
class ZeroCopyByteArray(bytearray):
def __reduce_ex__(self, protocol):
if protocol >= 5:
return type(self)._reconstruct, (PickleBuffer(self),), None
else:
# PickleBuffer запрещён для протоколов pickle <= 4.
return type(self)._reconstruct, (bytearray(self),)
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
# Получить дескриптор исходного буферного объекта.
obj = m.obj
if type(obj) is cls:
# Исходный буферный объект – это ZeroCopyByteArray, вернуть его.
# как есть.
return obj
else:
return cls(obj)
Восстановитель (метод класса _reconstruct) возвращает объект, предоставляющий буфер,
если он имеет правильный тип. Это простой способ имитировать поведение с нулевым копированием в этом игрушечном примере.
На стороне потребителя мы можем упаковать эти объекты обычным способом, который при десериализации даст нам копию исходного объекта:
b = ZeroCopyByteArray(b"abc")
data = pickle.dumps(b, protocol=5)
new_b = pickle.loads(data)
print(b == new_b) # True
print(b is new_b) # False: создана копия
Но если мы передадим buffer_callback, а затем вернём накопленные буферы при десериализации, мы сможем получить обратно исходный объект:
b = ZeroCopyByteArray(b"abc")
buffers = []
data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append)
new_b = pickle.loads(data, buffers=buffers)
print(b == new_b) # True
print(b is new_b) # True: копия не создана
Этот пример ограничен тем, что bytearray выделяет свою собственную память:
нельзя создать экземпляр bytearray, основанный на памяти другого объекта.
Однако сторонние типы данных, такие как массивы NumPy, не имеют этого ограничения
и позволяют использовать упаковку с нулевым копированием (или с минимальным количеством копий)
при передаче между разными процессами или системами.
См. также
PEP 574 – Протокол pickle 5 с внеполосными данными
Ограничение глобалов¶Restricting Globals
По умолчанию распаковщик импортирует любой класс или функцию, найденные в данных 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):
# Разрешать только безопасные классы из builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Запрещать всё остальное.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Вспомогательная функция, аналогичная pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
Пример использования нашего распаковщика, работающего как задумано:
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
... b'(S\'getattr(__import__("os"), "system")'
... b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'builtins.eval' is forbidden
Как показывают наши примеры, нужно быть осторожным с тем, что разрешается распаковывать. Поэтому если безопасность важна, стоит рассмотреть альтернативы, такие как marshalling API в модуле xmlrpc.client или сторонние решения.
Производительность¶Performance
Последние версии протокола pickle (начиная с протокола 2) поддерживают эффективное бинарное кодирование для ряда распространённых возможностей и встроенных типов. Кроме того, модуль pickle имеет прозрачный оптимизатор, написанный на C.
Примеры¶Examples
Для простейшего кода используйте функции dump() и load().
import pickle
# Произвольный набор объектов, поддерживаемых 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:
# Запаковать словарь 'data', используя самый высокий доступный протокол.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
Следующий пример читает полученные сериализованные данные.
import pickle
with open('data.pickle', 'rb') as f:
# Версия используемого протокола определяется автоматически, поэтому
# указывать её не нужно.
data = pickle.load(f)
Интерфейс командной строки¶Command-line interface
Модуль pickle может быть вызван как скрипт из командной строки; он отобразит содержимое файлов pickle. Однако если проверяемый pickle-файл получен из ненадёжного источника, -m pickletools является более безопасным вариантом, поскольку он не выполняет байткод pickle. См. использование CLI pickletools.
python -m pickle pickle_file [pickle_file ...]
Принимается следующий параметр:
- pickle_file¶
Pickle-файл для чтения, или
-для указания чтения из стандартного ввода.
См. также
- Модуль
copyreg Регистрация конструктора интерфейса pickle для расширенных типов.
- Модуль
pickletools Инструменты для работы и анализа сериализованных данных.
- Модуль
shelve Индексированные базы данных объектов; использует
pickle.- Модуль
copy Поверхностное и глубокое копирование объектов.
- Модуль
marshal Высокопроизводительная сериализация встроенных типов.
Сноски
Эта страница – перевод. Оригинал на английском – docs.python.org. Нашли неточность? Сообщите нам.