Spec-Zone.ru › Python 3.7

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

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

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

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

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

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

Сравнение с marshal

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

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

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

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

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

См. также

Модуль json: модуль стандартной библиотеки, позволяющий сериализацию и десериализацию JSON.

Формат потока данных

Формат данных, используемый модулем pickle, специфичен для Python. Это преимущество, так как нет ограничений, накладываемых внешними стандартами, такими как JSON или XDR (которые не могут представлять совместное использование указателей); однако это означает, что программы, не являющиеся программами Python, могут не быть в состоянии восстановить закодированные объекты Python.

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

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

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

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

Примечание

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

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

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

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

pickle.HIGHEST_PROTOCOL

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

pickle.DEFAULT_PROTOCOL

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

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

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

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

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

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

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

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

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

Аргументы protocol и fix_imports имеют то же значение, что и в dump().

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")

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

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

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

Необязательные ключевые аргументы — fix_imports, encoding и errors, которые используются для управления поддержкой совместимости с потоками pickle, сгенерированными Python 2. Если fix_imports имеет значение True, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Аргументы encoding и errors указывают pickle, как декодировать 8-битные строковые экземпляры, сериализованные Python 2; по умолчанию они равны ‘ASCII’ и ‘strict’ соответственно. encoding может быть 'bytes' для чтения этих 8-битных строковых экземпляров как объекты bytes. Для распаковки массивов NumPy и экземпляров datetime, date и time, сериализованных Python 2, требуется использование encoding='latin1'.

pickle.loads(data, *, fix_imports=True, encoding="ASCII", errors="strict")

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

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

Необязательные ключевые аргументы — fix_imports, encoding и errors, которые используются для управления поддержкой совместимости с потоками pickle, сгенерированными Python 2. Если fix_imports имеет значение True, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Аргументы encoding и errors указывают pickle, как декодировать 8-битные строковые экземпляры, сериализованные Python 2; по умолчанию они равны ‘ASCII’ и ‘strict’ соответственно. encoding может быть 'bytes' для чтения этих 8-битных строковых экземпляров как объекты bytes. Для распаковки массивов NumPy и экземпляров datetime, date и time, сериализованных Python 2, требуется использование encoding='latin1'.

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

exception pickle.PickleError

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

exception pickle.PicklingError

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

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

exception pickle.UnpicklingError

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

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

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

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

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

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

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

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

dump(obj)

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

persistent_id(obj)

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

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

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

dispatch_table

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

По умолчанию у объекта сериализатора не будет атрибута dispatch_table, и он будет использовать вместо него глобальную таблицу диспетчеризации, управляемую модулем copyreg. Однако для настройки сериализации для конкретного объекта сериализатора можно установить атрибут dispatch_table на объект, подобный словарю. Кроме того, если подкласс Pickler имеет атрибут dispatch_table, он будет использоваться в качестве таблицы диспетчеризации по умолчанию для экземпляров этого класса.

Примеры использования см. в разделе Таблицы диспетчеризации.

Введено в версии 3.3.

fast

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

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

class pickle.Unpickler(file, *, fix_imports=True, encoding="ASCII", errors="strict")

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

Версия протокола pickle определяется автоматически, поэтому аргумент protocol не требуется.

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

Необязательные ключевые аргументы — fix_imports, encoding и errors, которые используются для управления поддержкой совместимости с потоками pickle, сгенерированными Python 2. Если fix_imports имеет значение true, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Параметры encoding и errors сообщают pickle, как декодировать экземпляры строк с 8 битами, сериализованные Python 2; они по умолчанию равны ‘ASCII’ и ‘strict’ соответственно. Параметр encoding может быть ‘bytes’, чтобы читать эти строки с 8 битами как объекты bytes.

load()

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

persistent_load(pid)

По умолчанию вызывает UnpicklingError.

Если определён, persistent_load() должен вернуть объект, указанный идентификатором сохранения pid. При встрече некорректного идентификатора сохранения должно быть вызвано исключение UnpicklingError.

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

find_class(module, name)

Если необходимо, импортировать module и вернуть объект с именем name из него, где module и name — объекты str. Обратите внимание, что, вопреки своему названию, find_class() также используется для поиска функций.

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

Что можно сериализовать и десериализовать?

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

  • None, True, и False
  • целые, вещественные и комплексные числа
  • строки, байты, массивы байтов
  • кортежи, списки, множества и словари, содержащие только сериализуемые объекты
  • функции, определённые на верхнем уровне модуля (с помощью def, а не lambda)
  • встроенные функции, определённые на верхнем уровне модуля
  • классы, определённые на верхнем уровне модуля
  • экземпляры таких классов, чьё __dict__ или результат вызова __getstate__() сериализуемы (см. раздел Сериализация экземпляров классов для подробностей).

Попытки сериализовать несериализуемые объекты приведут к исключению PicklingError; в этом случае неопределённое количество байтов может быть записано в подлежащий файл. Попытка сериализации сильно рекурсивной структуры данных может превысить максимальную глубину рекурсии, в этом случае будет выброшено исключение RecursionError. Вы можете осторожно увеличить этот предел с помощью sys.setrecursionlimit().

Обратите внимание, что функции (встроенные и пользовательские) сериализуются по «полному квалифицированному» имени ссылки, а не по значению. 2 Это означает, что сериализуется только имя функции, а также имя модуля, в котором функция определена. Ни код функции, ни её атрибуты не сериализуются. Следовательно, определяющий модуль должен быть импортируемым в среде десериализации, и модуль должен содержать именованный объект, в противном случае будет выброшено исключение. 3

Аналогично, классы сериализуются по имени ссылки, поэтому в среде десериализации применяются те же ограничения. Обратите внимание, что ни код класса, ни данные не сериализуются, поэтому в следующем примере атрибут класса attr не восстанавливается в среде десериализации:

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

Эти ограничения обусловлены тем, что сериализуемые функции и классы должны быть определены на верхнем уровне модуля.

Аналогично, при сериализации экземпляров классов их код и данные не сериализуются вместе с ними. Сериализуются только данные экземпляра. Это сделано намеренно, чтобы вы могли исправить ошибки в классе или добавить методы к классу и при этом загрузить объекты, созданные с более ранней версией класса. Если вы планируете иметь долгоживущие объекты, которые будут видеть много версий класса, может быть целесообразно поместить номер версии в объекты, чтобы класс мог выполнить необходимые преобразования с помощью метода __setstate__().

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

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

В большинстве случаев для сериализации экземпляров не требуется дополнительный код. По умолчанию pickle извлекает класс и атрибуты экземпляра с помощью интроспекции. При десериализации экземпляра класса его метод __init__() обычно не вызывается. По умолчанию сначала создается неинициализированный экземпляр, а затем восстанавливаются сохранённые атрибуты. Следующий код демонстрирует реализацию этого поведения:

def save(obj):
    return (obj.__class__, obj.__dict__)

def load(cls, attributes):
    obj = cls.__new__(cls)
    obj.__dict__.update(attributes)
    return obj

Классы могут изменить поведение по умолчанию, предоставив один или несколько специальных методов:

object.__getnewargs_ex__()

В протоколах 2 и выше, классы, реализующие метод __getnewargs_ex__(), могут диктовать значения, передаваемые методу __new__() при десериализации. Метод должен возвращать пару (args, kwargs) , где args — кортеж позиционных аргументов, а kwargs — словарь именованных аргументов для построения объекта. Они будут переданы методу __new__() при десериализации.

Этот метод следует реализовать, если методу __new__() вашего класса необходимы только именованные аргументы. В противном случае, для обеспечения совместимости рекомендуется реализовать __getnewargs__().

Изменено в версии 3.6: __getnewargs_ex__() теперь используется в протоколах 2 и 3.

object.__getnewargs__()

Этот метод выполняет аналогичную задачу, что и __getnewargs_ex__(), но поддерживает только позиционные аргументы. Он должен возвращать кортеж аргументов args , который будет передан методу __new__() при десериализации.

Метод __getnewargs__() не будет вызван, если определён метод __getnewargs_ex__().

Изменено в версии 3.6: До Python 3.6, метод __getnewargs__() вызывался вместо __getnewargs_ex__() в протоколах 2 и 3.

object.__getstate__()

Классы могут дополнительно влиять на то, как сериализуются их экземпляры; если класс определяет метод __getstate__(), он вызывается, а возвращаемый объект сериализуется как содержимое экземпляра вместо содержимого словаря экземпляра. Если метод __getstate__() отсутствует, словарь экземпляра __dict__ сериализуется в обычном режиме.

object.__setstate__(state)

При десериализации, если класс определяет метод __setstate__(), он вызывается с десериализованным состоянием. В этом случае не требуется, чтобы состояние объекта было словарем. В противном случае сериализованное состояние должно быть словарем, и его элементы будут назначены в словарь нового экземпляра.

Примечание

Если метод __getstate__() возвращает ложное значение, метод __setstate__() не будет вызван при десериализации.

Дополнительную информацию о методах __getstate__() и __setstate__() см. в разделе Обработка объектов состояния.

Примечание

Во время десериализации некоторые методы, такие как __getattr__(), __getattribute__() или __setattr__(), могут быть вызваны для экземпляра. В случае, если эти методы полагаются на то, что внутреннее инвариантное свойство является истинным, тип должен реализовать __new__(), поскольку __init__() не вызывается при десериализации экземпляра.

Как мы увидим, pickle не использует напрямую описанные выше методы. На самом деле эти методы являются частью протокола копирования, который реализует специальный метод __reduce__(). Протокол копирования предоставляет унифицированный интерфейс для извлечения данных, необходимых для сериализации и копирования объектов. 4

Несмотря на мощь, реализация __reduce__() напрямую в ваших классах может привести к ошибкам. По этой причине разработчики классов должны использовать высокоуровневый интерфейс (т.е. __getnewargs_ex__(), __getstate__() и __setstate__()), когда это возможно. Однако мы покажем случаи, когда использование __reduce__() является единственным вариантом или приводит к более эффективной сериализации или обоим.

object.__reduce__()

Интерфейс в настоящее время определен следующим образом. Метод __reduce__() не принимает аргументов и должен возвращать либо строку, либо, предпочтительно, кортеж (возвращаемый объект часто называется «значением reduce»).

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

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

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

В качестве альтернативы, может быть определен метод __reduce_ex__(). Единственное различие состоит в том, что этот метод должен принимать один целочисленный аргумент — версию протокола. При определении pickle будет предпочитать его методу __reduce__(). Кроме того, метод __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, выглядит так

copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)

Обработка состояний объектов

Вот пример, демонстрирующий, как изменить поведение сериализации для класса. Класс 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!'

Ограничение глобальных переменных

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

>>> 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, 4+6j],
    'b': ("character string", b"byte string"),
    'c': {None, True, False}
}

with open('data.pickle', 'wb') as f:
    # Pickle the 'data' dictionary using the highest protocol available.
    pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)

Следующий пример считывает полученные сериализованные данные.

import pickle

with open('data.pickle', 'rb') as f:
    # The protocol version used is detected automatically, so we do not
    # have to specify it.
    data = pickle.load(f)

См. также

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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/pickle.html

Spec-Zone.ru

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