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


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

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

Модуль pickle не безопаcен. Анпиклите только те данные, которым доверяете.

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

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

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

Отношение к другим Python модулям

Сравнение с marshal

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

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

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

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

  • marshal нельзя использовать для сериализации пользовательских классов и их экземпляров. pickle может сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при сохранении объекта.

  • Переносимость формата сериализации marshal между версиями Python не гарантируется. Поскольку его основная задача заключается в поддержке .pyc файлов, разработчики Python оставляют за собой право изменять формат сериализации несовместимыми способами, если возникнет такая необходимость. Формат сериализации pickle гарантированно обратно совместим между Python релизами при условии, что выбран совместимый протокол pickle, а код пиклинга и анпиклинга работает с различиями типов Python 2 и Python 3, если ваши данные пересекают эту уникальную границу языковых изменений.

Сравнение с json

Между протоколами pickle и JSON (JavaScript Object Notation): существуют фундаментальные различия:

  • JSON — это формат сериализации текста (он выводит текст в формате Юникод, хотя большую часть времени он закодирован в utf-8), а pickle — это двоичный формат сериализации;
  • JSON удобочитаемый, а pickle — нет;
  • JSON совместим и широко используется за пределами экосистемы Python, в то время как pickle специфичен для Python;
  • JSON по умолчанию может представлять только подмножество встроенных типов Python и не может представлять собой пользовательские классы; pickle может представлять чрезвычайно большое количество типов Python (многие из них автоматически, с умным использованием средств самоанализа Python; сложные случаи могут быть решены путём реализации API объектов) ;
  • В отличие от pickle, десериализация ненадежного JSON сама по себе не создаёт уязвимости при выполнении произвольного кода.

См.также

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

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

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

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

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

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

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

Примечание

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

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

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

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

pickle.HIGHEST_PROTOCOL

Целое число, максимальное возможное значение версии протокола. Это значение можно передать как значение 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 определяется автоматически, поэтому аргумент протокола не требуется. Байты после пиклинга представления объекта игнорируются.

Аргументы 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 определяется автоматически, поэтому аргумент протокола не требуется. Байты после пиклинга представления объекта игнорируются.

Аргументы file, 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 (целое число) указывает пиклеру использовать данный протокол; поддерживаемые протоколы: от 0 до HIGHEST_PROTOCOL. Если не указан, по умолчанию используется DEFAULT_PROTOCOL. Если указано отрицательное число, выбирается HIGHEST_PROTOCOL.

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

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

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

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

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

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

dump(obj)

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

persistent_id(obj)

По умолчанию ничего не делать. Он существует, поэтому подкласс может его переопределить.

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

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

dispatch_table

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

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

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

Добавлено в версии 3.3.

reducer_override(self, obj)

Специальный редюсер, который может быть определен в подклассах Pickler. Этот метод имеет приоритет над любым редюсером в dispatch_table. Он должен соответствовать тому же интерфейсу, что и метод __reduce__(), и может при желании возвращать NotImplemented для возврата на зарегистрированные dispatch_table редюсеры для pickle obj.

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

Добавлено в версии 3.8.

fast

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

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

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 может быть «байтовым» для чтения этих 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() должен возвращать объект, указанный постоянным ID pid. Если обнаружен недопустимый постоянный ID, должно вызваться UnpicklingError.

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

find_class(module, name)

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

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

Вызывает событие аудита pickle.find_class с аргументами module, name.

class pickle.PickleBuffer(buffer)

Обёртка для буфера, представляющего пиклингуемые данные. buffer должен быть буферным объектом, например байтоподобный объект или N-мерным массивом.

PickleBuffer сам по себе является поставщиком буфера, поэтому его можно передать другим API, ожидающим объекта, предоставляющего буфер, например memoryview.

Объекты PickleBuffer можно сериализовать только с использованием протокола pickle 5 или выше. Они имеют право на внеполосную сериализацию.

Добавлено в версии 3.8.

raw()

Возвращает memoryview области памяти, лежащей в основе этого буфера. Возвращенный объект представляет собой одномерное непрерывное представление памяти C в формате B (беззнаковые байты). Вызывается BufferError, если буфер не является ни C, ни Fortran смежным.

release()

Освобождает нижележащий буфер, предоставленный PickleBuffer объектом.

Что можно пиклить, а что нет?

Можно пиклить следующие типы:

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

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

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

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

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

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

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

Пиклинг экземпляров классов

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

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

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

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

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

Когда возвращается кортеж, он должен содержать от двух до шести элементов. Необязательные элементы можно либо опустить, либо указать 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__() автоматически становится синонимом расширенной версии. Основное использование этого метода — предоставление обратно совместимых сокращенных значений для старых версий Python.

Постоянство внешних объектов

Для обеспечения постоянства объекта модуль pickle поддерживает понятие ссылки на объект вне потока пиклинга данных. На такие объекты ссылается постоянный ID, который должен быть либо строкой буквенно-цифровых символов (для протокола 0) [5], либо просто произвольным объектом (для любого нового протокола).

Разрешение таких постоянных ID не определяется модулем pickle; он делегирует это разрешение определенным пользователем методам средства выбора и удаления, persistent_id() и persistent_load() соответственно.

Чтобы пиклить объекты с внешним постоянным ID, пиклер должно содержать собственный метод persistent_id(), который принимает объект в качестве аргумента и возвращает либо None, либо постоянный ID этого объекта. Когда возвращается None, пиклер просто пиклит объект как обычно. Когда возвращается ID строки, пиклер пиклит этот объект вместе с маркером, чтобы анпиклер распознавал его как постоянный ID.

Чтобы анпиклить внешние объекты, у анпиклера должен быть специальный метод persistent_load(), который принимает постоянный ID объекта и возвращает объект, на который имеется ссылка.

Вот исчерпывающий пример, показывающий, как постоянный ID может использоваться для пиклинга внешних объектов по ссылке.

# Простой пример, показывающий, как постоянный идентификатор может использоваться
# для выбора внешних объектов по ссылке.

import pickle
import sqlite3
from collections import namedtuple

# Простой класс, представляющий запись в нашей базе данных.
MemoRecord = namedtuple("MemoRecord", "key, task")

class DBPickler(pickle.Pickler):

    def persistent_id(self, obj):
        # Вместо того, чтобы пиклить MemoRecord как обычный экземпляр класса, мы
        # генерируем постоянный ID.
        if isinstance(obj, MemoRecord):
            # Здесь наш постоянный ID --- это просто кортеж, содержащий тег и
            # ключ, который относится к определенной записи в базе данных.
            return ("MemoRecord", obj.key)
        else:
            # Если obj не имеет постоянного ID, вернуть None. Это означает, что
            # obj нужно пиклить как обычно.
            return None


class DBUnpickler(pickle.Unpickler):

    def __init__(self, file, connection):
        super().__init__(file)
        self.connection = connection

    def persistent_load(self, pid):
        # Этот метод вызывается всякий раз, когда встречается постоянный ID.
        # Здесь pid - это кортеж, возвращаемый DBPickler.
        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:
            # Всегда вызывает ошибку, если вы не можете вернуть правильный объект. В
            # противном случае анпиклер будет думать, что None --- это объект, на который
            # ссылается постоянный ID.
            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]
    # Сохранить записи с помощью нашего настраиваемого DBPickler.
    file = io.BytesIO()
    DBPickler(file).dump(memos)

    print("Pickled records:")
    pprint.pprint(memos)

    # Вернуть запись, на всякий случай.
    cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")

    # Загрузить записи из потока данных pickle.
    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:
    """Распечатать и пронумеровать строки в текстовом файле."""

    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):
        # Скопировать состояние объекта из self.__ dict__, который содержит все атрибуты
        # нашего экземпляра. Всегда используйте метод dict.copy(), чтобы избежать
        # изменения исходного состояния.
        state = self.__dict__.copy()
        # Удалить непикабл записи.
        del state['file']
        return state

    def __setstate__(self, state):
        # Восстановить атрибуты экземпляра (например, имя файла и текстуру).
        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!'

Пользовательская редукция для типов, функций и других объектов

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

Внеполосные буферы

Добавлено в версии 3.8.

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

Этого ограничения можно избежать, если и провайдер (реализация типов объектов для передачи), и потребитель (реализация системы связи) поддерживают средства внеполосной передачи, обеспечиваемые протоколом pickle 5 и выше.

API провайдера

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

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

Потребительское API

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

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

На принимающей стороне ему необходимо передать аргумент buffers в Unpickler (или в функцию load() или loads()), которая является итерацией буферов, переданных в buffer_callback. Итерация должна создавать буферы в том же порядке, в каком они были переданы в buffer_callback. Эти буферы предоставят данные, ожидаемые реконструкторами объектов, при пиклинге которых были получены исходные объекты PickleBuffer.

Между отправляющей и принимающей сторонами система связи может реализовать свой собственный механизм передачи для внеполосных буферов. Возможные оптимизации включают использование разделяемой памяти или сжатия в зависимости от типа данных.

Пример

Далее тривиальный пример, в котором мы реализуем подкласс bytearray, способный участвовать во внеполосном пиклинге буфера:

class ZeroCopyByteArray(bytearray):

    def __reduce_ex__(self, protocol):
        if protocol >= 5:
            return type(self)._reconstruct, (PickleBuffer(self),), None
        else:
            # PickleBuffer запрещен с протоколами 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 – протокол 5 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):
        # Разрешить только безопасные классы из встроенных.
        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

Как показывают наши примеры, вы должны быть осторожны с тем, что вы позволяете анпиклить. Поэтому, если безопасность является проблемой, вы можете рассмотреть альтернативы, такие как API маршаллинга в xmlrpc.client или сторонние решения.

Производительность

Последние версии протокола pickle (начиная с протокола 2 и выше) содержат эффективные двоичные кодировки для нескольких общих функций и встроенных типов. Также модуль pickle имеет прозрачный оптимизатор, написанный на C.

Примеры

Для простейшего кода используйте функции dump() и load().

import pickle

# Произвольный набор объектов, поддерживаемый 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.dump(data, f, pickle.HIGHEST_PROTOCOL)

В следующем примере читаются полученные в результате пиклед данные.

import pickle

with open('data.pickle', 'rb') as f:
    # Используемая версия протокола определяется автоматически,
    # поэтому указывать её не нужно.
    data = pickle.load(f)

См.также

Module copyreg
Регистрация конструктора интерфейса Pickle для типов расширений.
Модуль pickletools
Инструменты для работы и анализа пиклед данных.
Модуль shelve
Индексированные базы данных объектов; использует pickle.
Модуль copy
Поверхностное и глубокое копирование объектов.
Модуль marshal
Высокопроизводительная сериализация встроенных типов.

Сноски

[1]Не путайте с модулем marshal
[2]Вот почему функции lambda не могут быть пиклед: все функции lambda имеют одно и то же имя: <lambda>.
[3]Вызванное исключение, вероятно, будет ImportError или AttributeError но это может быть что-то другое.
[4]Модуль copy использует этот протокол для операций поверхностного и глубокого копирования.
[5]Ограничение на буквенно-цифровые символы связано с тем, что постоянные ID’ы, в протоколе 0, ограничиваются символом новой строки. Поэтому, если в постоянных ID’ов встречаются какие-либо символы новой строки, результирующий пикл станет нечитаемым.