Spec-Zone.ru › Python 3.13

pickle — Сериализация объектов Python

Исходный код: Lib/pickle.py

Модуль pickle реализует двоичные протоколы для сериализации и десериализации структуры Python-объектов. «Заморозка» (pickling) — процесс преобразования иерархии Python-объектов в поток байтов, а «разморозка» (unpickling) — обратная операция, при которой поток байтов (из бинарного файла или объекта типа bytes) преобразуется обратно в иерархию объектов. Заморозка (и разморозка) альтернативно известна как «сериализация», «маршаллинг», [1] или «уплощение»; однако, чтобы избежать путаницы, здесь используются термины «заморозка» и «разморозка».

Предупреждение

Модуль pickle небезопасен. Размораживайте только данные, которым доверяете.

Возможно создание вредоносных данных pickle, которые выполнят произвольный код во время разморозки. Никогда не размораживайте данные, которые могут быть получены из недоверенного источника или изменены.

Если необходимо убедиться, что данные не были изменены, рассмотрите возможность их подписи с помощью hmac.

Для обработки недоверенных данных могут быть более подходящими более безопасные форматы сериализации, такие как json. См. Сравнение с json.

Связь с другими модулями Python

Сравнение с marshal

В Python есть более примитивный модуль сериализации под названием marshal, но в целом pickle всегда должен быть предпочтительным способом сериализации Python-объектов. marshal существует в основном для поддержки .pyc файлов Python.

Модуль pickle отличается от marshal по нескольким важным аспектам:

  • Модуль pickle отслеживает объекты, которые он уже сериализовал, так что последующие ссылки на тот же объект не будут сериализованы повторно. marshal этого не делает.

    Это имеет последствия как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты — это объекты, содержащие ссылки на себя. Они не обрабатываются marshal, и попытка маршаллить рекурсивные объекты приведет к сбою интерпретатора Python. Совместное использование объектов происходит, когда в разных местах иерархии сериализуемых объектов есть несколько ссылок на один и тот же объект. pickle сохраняет такие объекты только один раз и гарантирует, что все другие ссылки указывают на главный экземпляр. Общие объекты остаются общими, что может быть очень важно для изменяемых объектов.

  • marshal нельзя использовать для сериализации пользовательских классов и их экземпляров. pickle может сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при хранении объекта.
  • Формат сериализации marshal не гарантирует переносимость между версиями Python. Поскольку его основная задача — поддержка .pyc файлов, разработчики Python оставляют за собой право изменять формат сериализации несовместимым образом, если это потребуется. Формат сериализации pickle гарантирует обратную совместимость между выпусками Python при условии выбора совместимого протокола pickle и обработки кодом pickling и unpickling различий в типах 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 (который не может представлять совместное использование указателей); однако это означает, что программы, не написанные на Python, могут не иметь возможности восстановить сериализованные объекты Python.

По умолчанию формат данных модуля pickle использует относительно компактное двоичное представление. Если вам необходимы оптимальные характеристики размера, вы можете эффективно сжать сериализованные данные.

Модуль pickletools содержит инструменты для анализа потоков данных, генерируемых модулем pickle. Исходный код модуля pickletools содержит обширные комментарии об операциях, используемых протоколами сериализации.

В настоящее время существует 6 различных протоколов, которые можно использовать для сериализации. Чем выше используемый протокол, тем более свежая версия Python необходима для чтения созданного файла сериализации.

  • Протокол версии 0 — это исходный «человекочитаемый» протокол и обратно совместим с более ранними версиями Python.
  • Протокол версии 1 — это старый двоичный формат, также совместимый с более ранними версиями Python.
  • Протокол версии 2 был представлен в Python 2.3. Он обеспечивает более эффективную сериализацию классов нового стиля. Для получения информации об улучшениях, внесенных протоколом 2, обратитесь к PEP 307.
  • Протокол версии 3 был добавлен в Python 3.0. Он имеет явную поддержку объектов bytes и не может быть десериализован Python 2.x. Это был протокол по умолчанию в Python 3.0–3.7.
  • Протокол версии 4 был добавлен в Python 3.4. Он добавляет поддержку очень больших объектов, сериализацию большего количества типов объектов и некоторые оптимизации формата данных. Он является протоколом по умолчанию начиная с Python 3.8. Для получения информации об улучшениях, внесенных протоколом 4, обратитесь к PEP 3154.
  • Протокол версии 5 был добавлен в Python 3.8. Он добавляет поддержку внеполосных данных и ускорение для внутриполосных данных. Для получения информации об улучшениях, внесенных протоколом 5, обратитесь к PEP 574.

Примечание

Сериализация — это более примитивное понятие, чем персистентность; хотя модуль pickle читает и записывает файлы, он не решает проблему именования постоянных объектов, а также (еще более сложную) проблему одновременного доступа к постоянным объектам. Модуль pickle может преобразовать сложный объект в поток байтов и преобразовать поток байтов в объект с той же внутренней структурой. Возможно, наиболее очевидным действием с этими потоками байтов является их запись в файл, но также возможно их отправлять по сети или хранить в базе данных. Модуль shelve предоставляет простой интерфейс для сериализации и десериализации объектов в файлах баз данных в стиле DBM.

Интерфейс модуля

Для сериализации иерархии объектов просто вызовите функцию dumps(). Аналогично, для десериализации потока данных вызовите функцию loads(). Однако, если вы хотите получить больший контроль над сериализацией и десериализацией, вы можете создать объект Pickler или Unpickler, соответственно.

Модуль pickle предоставляет следующие константы:

pickle.HIGHEST_PROTOCOL

Целое число, максимальная доступная версия протокола. Это значение может быть передано в качестве значения protocol функциям dump() и dumps(), а также конструктору Pickler.

pickle.DEFAULT_PROTOCOL

Целое число, по умолчанию версия протокола используется для сериализации. Может быть меньше, чем HIGHEST_PROTOCOL. В настоящее время протокол по умолчанию — 4, впервые представленный в Python 3.4 и несовместимый с предыдущими версиями.

Изменено в версии 3.0: Протокол по умолчанию — 3.

Изменено в версии 3.8: Протокол по умолчанию — 4.

Модуль pickle предоставляет следующие функции для повышения удобства процесса сериализации:

pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)

Записать сериализованное представление объекта obj в открытый объект файла file. Это эквивалентно Pickler(file, protocol).dump(obj).

Аргументы file, protocol, fix_imports и buffer_callback имеют то же значение, что и в конструкторе Pickler.

Изменено в версии 3.8: Добавлен аргумент buffer_callback.

pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)

Вернуть сериализованное представление объекта obj в виде объекта bytes, вместо записи в файл.

Аргументы protocol, fix_imports и buffer_callback имеют то же значение, что и в конструкторе Pickler.

Изменено в версии 3.8: Добавлен аргумент buffer_callback.

pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

Прочитать сериализованное представление объекта из открытого объекта файла file и вернуть воссозданную иерархию объектов, указанную в нём. Это эквивалентно Unpickler(file).load().

Версия протокола pickle обнаруживается автоматически, поэтому аргумент protocol не нужен. Байты после сериализованного представления объекта игнорируются.

Аргументы file, fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе Unpickler.

Изменено в версии 3.8: Добавлен аргумент buffers.

pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

Вернуть воссозданную иерархию объектов из сериализованного представления data объекта. data должен быть объектом типа bytes-like object.

Версия протокола pickle обнаруживается автоматически, поэтому аргумент protocol не нужен. Байты после сериализованного представления объекта игнорируются.

Аргументы fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе Unpickler.

Изменено в версии 3.8: Добавлен аргумент buffers.

Модуль pickle определяет три исключения:

exception pickle.PickleError

Базовый класс для других исключений сериализации. Наследуется от Exception.

exception pickle.PicklingError

Исключение, генерируемое, когда Pickler встречает несериализуемый объект. Наследуется от PickleError.

См. Что можно сериализовать и десериализовать?, чтобы узнать, какие типы объектов можно сериализовать.

exception pickle.UnpicklingError

Исключение, генерируемое при проблемах с десериализацией объекта, таких как повреждение данных или нарушение безопасности. Наследуется от PickleError.

Обратите внимание, что при десериализации могут быть сгенерированы и другие исключения, включая (но не ограничиваясь ими) AttributeError, EOFError, ImportError и IndexError.

Модуль pickle экспортирует три класса: Pickler, Unpickler и PickleBuffer.

class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None)

Это принимает бинарный файл для записи потока данных pickle.

Необязательный аргумент protocol, целое число, сообщает pickler использовать указанный протокол; поддерживаемые протоколы — от 0 до HIGHEST_PROTOCOL. Если не указано, по умолчанию используется DEFAULT_PROTOCOL. Если указано отрицательное число, выбирается HIGHEST_PROTOCOL.

Аргумент file должен иметь метод write(), принимающий один аргумент типа bytes. Таким образом, он может быть файлом на диске, открытым для двоичного записи, экземпляром io.BytesIO или любым другим пользовательским объектом, соответствующим этому интерфейсу.

Если fix_imports истинно и protocol меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, использовавшимися в Python 2, чтобы поток данных pickle был читаем с Python 2.

Если buffer_callback равен None (по умолчанию), представления буферов сериализуются в file как часть потока pickle.

Если buffer_callback не равен None, то он может вызываться любое количество раз с представлением буфера. Если обратный вызов возвращает ложное значение (например, None), указанный буфер является вне зоны действия; в противном случае буфер сериализуется в пределах потока pickle, то есть внутри потока pickle.

Возникает ошибка, если buffer_callback не равен None и protocol равен None или меньше 5.

Изменено в версии 3.8: Аргумент buffer_callback был добавлен.

dump(obj)

Записать сериализованное представление obj в открытый файл, указанный в конструкторе.

persistent_id(obj)

По умолчанию ничего не делает. Это существует для того, чтобы подкласс мог переопределить его.

Если persistent_id() возвращает None, obj сериализуется как обычно. Любое другое значение заставляет Pickler вывести возвращаемое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно быть определено Unpickler.persistent_load(). Обратите внимание, что значение, возвращаемое persistent_id(), само по себе не может иметь постоянный идентификатор.

Подробности и примеры использования см. в разделе Сохранение внешних объектов.

Изменено в версии 3.13: Добавлена реализация по умолчанию этого метода в C-реализации Pickler.

dispatch_table

Таблица диспетчеризации объекта pickler — это регистр *функций редукции* типа, который можно объявить, используя copyreg.pickle(). Это отображение, ключи которого — классы, а значения — функции редукции. Функция редукции принимает один аргумент связанного класса и должна соответствовать тому же интерфейсу, что и метод __reduce__().

По умолчанию у объекта pickler не будет атрибута 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

Устарело. Включить быстрый режим, если значение установлено в истину. Быстрый режим отключает использование memo, поэтому ускоряет процесс сериализации, не генерируя излишних команд PUT. Его не следует использовать с объектами, ссылающимися на себя, поскольку в противном случае Pickler будет рекурсивно вызываться бесконечно.

Используйте pickletools.optimize(), если вам нужны более компактные pickle.

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-битные строковые объекты как байтовые объекты. Для распаковки массивов NumPy и экземпляров datetime, date и time, закодированных Python 2, требуется использование encoding='latin1'.

Если buffers равно None (значение по умолчанию), то все данные, необходимые для десериализации, должны быть содержаться в потоке pickle. Это означает, что аргумент buffer_callback был None при создании Pickler (или при вызове dump() или dumps()).

Если buffers не равно None, оно должно быть итерируемым объектом с поддержкой буферов, который используется каждый раз, когда поток pickle ссылается на внеполосный просмотр буфера. Такие буферы были предоставлены объекту Pickler в качестве параметра buffer_callback.

Изменено в версии 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.

Что можно сохранить и восстановить?

Следующие типы можно сохранить:

  • встроенные константы (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__().

Сериализация экземпляров классов

В этом разделе мы описываем общие механизмы, доступные для определения, настройки и управления тем, как экземпляры классов сериализуются и десериализуются.

В большинстве случаев для обеспечения сериализуемости экземпляров дополнительный код не требуется. По умолчанию 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__(). Он вызывается, и возвращаемый объект сериализуется как содержимое экземпляра вместо состояния по умолчанию. Рассмотрим несколько случаев:

  • Для класса, у которого нет атрибута __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__() не принимает аргументов и должен вернуть либо строку, либо, предпочтительно, кортеж (возвращаемый объект часто называют «значением уменьшения»).

Если возвращается строка, строка должна интерпретироваться как имя глобальной переменной. Оно должно соответствовать локальному имени объекта относительно его модуля; модуль pickle ищет пространство имён модуля, чтобы определить модуль объекта. Это поведение обычно полезно для синглтонов.

Когда возвращается кортеж, его длина должна быть от двух до шести элементов. Дополнительные элементы могут быть опущены или None в качестве их значения. Семантика каждого элемента приведена ниже:

  • Объект вызываемого типа, который будет вызван для создания начальной версии объекта.
  • Кортеж аргументов для вызываемого объекта. Если вызываемый объект не принимает аргументов, должен быть указан пустой кортеж.
  • Необязательно, состояние объекта, которое будет передано методу объекта __setstate__(), как описано ранее. Если у объекта нет такого метода, значение должно быть словарем, и оно будет добавлено в атрибут объекта __dict__.
  • Необязательно, итератор (а не последовательность), возвращающий последовательные элементы. Эти элементы будут добавлены к объекту с помощью obj.append(item) или, по частям, с помощью obj.extend(list_of_items). Это в основном используется для подклассов списков, но может использоваться и другими классами, если у них есть методы append и extend с соответствующей сигнатурой. (Использование append() или extend() зависит от версии протокола pickle и количества добавляемых элементов, поэтому обе возможности должны поддерживаться.)
  • Необязательно, итератор (не последовательность), возвращающий последовательные пары ключ-значение. Эти элементы будут сохранены в объекте с помощью obj[key] = value. Это в основном используется для подклассов словарей, но может использоваться и другими классами, если они реализуют __setitem__().
  • Необязательно, вызываемый объект с сигнатурой (obj, state). Этот вызываемый объект позволяет пользователю программно управлять поведением обновления состояния конкретного объекта вместо использования статического метода obj __setstate__(). Если не None, этот вызываемый объект будет иметь приоритет над методом __setstate__() объекта obj.

    Добавлен в версии 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 версии 5 и выше.

API поставщика

Объекты с большими данными, подлежащие сериализации, должны реализовывать метод __reduce_ex__(), специализированный для протокола 5 и выше, который возвращает экземпляр PickleBuffer (вместо, например, объекта bytes) для любых больших данных.

Объект PickleBuffer указывает, что базовый буфер подходит для передачи данных вне зоны обмена. Эти объекты остаются совместимыми с обычным использованием модуля pickle. Однако получатели также могут включить возможность сообщить модулю pickle, что они будут обрабатывать эти буферы самостоятельно.

API получателя

Система связи может активировать пользовательскую обработку объектов PickleBuffer, сгенерированных при сериализации графа объектов.

В сторону отправки необходимо передать аргумент buffer_callback в Pickler (или в функции dump() или dumps()), который будет вызываться с каждым сгенерированным объектом PickleBuffer во время сериализации графа объектов. Буферы, накопленные функцией buffer_callback, не будут копировать свои данные в поток сериализации, будет вставлен только недорогой маркер.

В сторону приема необходимо передать аргумент 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)

См. также

Module copyreg

Регистрация конструкторов интерфейса pickle для типов расширений.

Module pickletools

Инструменты для работы с сериализованными данными и их анализа.

Module shelve

Индексированные базы данных объектов; использует pickle.

Module copy

Поверхностное и глубокое копирование объектов.

Module marshal

Высокопроизводительная сериализация встроенных типов.

Примечания

[1]

Не путайте это с модулем marshal

[2]

Вот почему анонимные функции lambda нельзя сериализовать: все функции lambda имеют одно и то же имя: <lambda>.

[3]

Возникающее исключение, скорее всего, будет ImportError или AttributeError, но это может быть и что-то другое.

[4]

Модуль copy использует этот протокол для поверхностного и глубокого копирования.

[5]

Ограничение на символы только из алфавита и цифр вызвано тем, что идентификаторы постоянных объектов в протоколе 0 разделяются символом новой строки. Поэтому, если в идентификаторах постоянных объектов встречаются символы новой строки, результирующие сериализованные данные будут нечитаемыми.

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/pickle.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API