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

Источник: Lib/pickle.py


Модуль pickle реализует двоичные протоколы для сериализации и де-сериализации объектной структуры Python. «Pickling „ - это процесс, при котором иерархия объектов Python преобразуется в поток байтов, а “unpickling » - это обратная операция, при которой поток байтов (из binary file или bytes-like object) преобразуется обратно в иерархию объектов. Пикировка (и распикировка) альтернативно известна как «сериализация», «маршаллинг», [1] или «сплющивание»; однако, чтобы избежать путаницы, здесь используются термины «пикировка» и «распикировка».

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

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

Можно создать вредоносные данные 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; сложные случаи могут быть решены путем реализации specific object APIs);

  • В отличие от pickle, десериализация недоверенного JSON сама по себе не создает уязвимости для выполнения произвольного кода.

См.также

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

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

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

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

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

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

  • Протокол версии 0 является оригинальным «человекочитаемым» протоколом и обратно совместим с предыдущими версиями Python.

  • Протокол версии 1 - это старый двоичный формат, который также совместим с ранними версиями Python.

  • Протокол версии 2 был представлен в Python 2.3. Он обеспечивает гораздо более эффективную сортировку new-style classes. Информацию об улучшениях, внесенных протоколом 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 предоставляет простой интерфейс для pickle и unpickle объектов в файлах баз данных в стиле DBM.

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

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

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

pickle.HIGHEST_PROTOCOL

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

pickle.DEFAULT_PROTOCOL

Целое число, по умолчанию protocol version, используемое для маринования. Может быть меньше 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 object файл. Это эквивалентно 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 object файла и возвращает восстановленную иерархию объектов, указанную в нем. Это эквивалентно Unpickler(file).load().

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

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

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

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: Был добавлен аргумент буферы.

Модуль 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 равно true и protocol меньше 3, pickle попытается сопоставить новые имена модулей Python 3 со старыми именами модулей, используемых в Python 2, чтобы поток данных pickle можно было читать с помощью Python 2.

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

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

Ошибкой будет, если buffer_callback не None и 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: Добавьте реализацию этого метода по умолчанию в реализацию Pickler на языке C.

dispatch_table

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

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

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

Added in version 3.3.

reducer_override(obj)

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

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

Added in version 3.8.

fast

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

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

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

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

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

Аргумент 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“, соответственно. Кодировка* может быть „bytes“, чтобы читать эти 8-битные экземпляры строк как байтовые объекты. Использование encoding='latin1' необходимо для распаковки массивов NumPy и экземпляров datetime, date и time, замаринованных Python 2.

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

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

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

load()

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

persistent_load(pid)

По умолчанию поднимает UnpicklingError.

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

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

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

find_class(module, name)

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

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

Поднимает auditing event pickle.find_class с аргументами module, name.

class pickle.PickleBuffer(buffer)

Обертка для буфера, представляющего picklable-данные. Буфер должен быть объектом buffer-providing, например bytes-like object или N-мерным массивом.

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

Объекты PickleBuffer могут быть сериализованы только с помощью протокола pickle 5 или выше. Они могут быть использованы для out-of-band serialization.

Added in version 3.8.

raw()

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

release()

Освобождение базового буфера, открываемого объектом PickleBuffer.

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

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

  • встроенные константы (None, True, False, Ellipsis и NotImplemented);

  • целые числа, числа с плавающей запятой, комплексные числа;

  • строки, байты, байтовые массивы;

  • кортежи, списки, множества и словари, содержащие только picklable-объекты;

  • функции (встроенные и определяемые пользователем), доступные с верхнего уровня модуля (с использованием def, а не lambda);

  • классы, доступные с верхнего уровня модуля;

  • экземпляры таких классов, у которых результат вызова __getstate__() является picklable (подробнее см. раздел Экземпляры класса Pickling).

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

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

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

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

Именно из-за этих ограничений picklable-функции и классы должны быть определены на верхнем уровне модуля.

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

Экземпляры класса Pickling

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

В большинстве случаев для того, чтобы сделать экземпляры picklable, не требуется никакого дополнительного кода. По умолчанию 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 в протоколах 2 и 3 вместо __getnewargs_ex__() вызывался __getnewargs__().

object.__getstate__()

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

  • Для класса, у которого нет экземпляра __dict__ и __slots__, состоянием по умолчанию будет None.

  • Для класса, у которого есть экземпляр __dict__ и нет __slots__, состоянием по умолчанию будет self.__dict__.

  • Для класса, имеющего экземпляры __dict__ и __slots__, состоянием по умолчанию является кортеж, состоящий из двух словарей: self.__dict__ и словаря, отображающего имена слотов на их значения. В последний включаются только те слоты, которые имеют значение.

  • Для класса, у которого есть __slots__ и нет экземпляра __dict__, состоянием по умолчанию является кортеж, первым элементом которого является None, а вторым - словарь, отображающий имена слотов на значения слотов, описанные в предыдущем пункте.

Изменено в версии 3.11: Добавлена реализация метода __getstate__() по умолчанию в классе object.

object.__setstate__(state)

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

Примечание

Если при пикировке __reduce__() возвращает состояние со значением None, то метод __setstate__() не будет вызван при распикировке.

Подробнее о том, как использовать методы __getstate__() и __setstate__(), читайте в разделе Работа с объектами с состоянием.

Примечание

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

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

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

object.__reduce__()

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

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

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

  • Вызываемый объект, который будет вызван для создания начальной версии объекта.

  • Кортеж аргументов для объекта callable. Если вызываемый объект не принимает ни одного аргумента, необходимо указать пустой кортеж.

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

  • Опционально, итератор (а не последовательность), дающий последовательные элементы. Эти элементы будут добавлены к объекту либо с помощью obj.append(item), либо, в пакетном режиме, с помощью obj.extend(list_of_items). В основном это используется в подклассах списков, но может использоваться и другими классами, если у них есть append and extend methods с соответствующей сигнатурой. (Использование append() или extend() зависит от версии протокола pickle, а также от количества добавляемых элементов, поэтому оба варианта должны поддерживаться).

  • Опционально, итератор (не последовательность), содержащий последовательные пары ключ-значение. Эти элементы будут сохранены в объекте с помощью obj[key] = value. В основном это используется в подклассах словарей, но может использоваться и другими классами, если они реализуют __setitem__().

  • Опционально, вызываемый объект с сигнатурой (obj, state). Этот вызываемый объект позволяет пользователю программно контролировать поведение обновления состояния конкретного объекта, вместо того чтобы использовать статический метод obj. Если не None, то этот вызываемый объект будет иметь приоритет перед obj __setstate__().

    Added in version 3.8: Был добавлен необязательный шестой элемент кортежа, (obj, state).

object.__reduce_ex__(protocol)

В качестве альтернативы можно определить метод __reduce_ex__(). Единственное отличие заключается в том, что этот метод должен принимать единственный целочисленный аргумент - версию протокола. Если он определен, pickle предпочтет его методу __reduce__(). Кроме того, __reduce__() автоматически становится синонимом расширенной версии. Основное назначение этого метода - обеспечить обратную совместимость значений reduce для старых версий Python.

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

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

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

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

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

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

# 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 замаринован, сохраняются все атрибуты, за исключением члена объекта file. Когда экземпляр распаковывается, файл открывается снова, и чтение возобновляется с последнего места. Для реализации этого поведения используются методы __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!'

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

Added in version 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

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

Added in version 3.8.

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

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

API провайдера

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

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

API для потребителей

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

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

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

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

Пример

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

class ZeroCopyByteArray(bytearray):

    def __reduce_ex__(self, protocol):
        if protocol >= 5:
            return type(self)._reconstruct, (PickleBuffer(self),), None
        else:
            # PickleBuffer is forbidden with pickle protocols <= 4.
            return type(self)._reconstruct, (bytearray(self),)

    @classmethod
    def _reconstruct(cls, obj):
        with memoryview(obj) as m:
            # Get a handle over the original buffer object
            obj = m.obj
            if type(obj) is cls:
                # Original buffer object is a ZeroCopyByteArray, return it
                # as-is.
                return obj
            else:
                return cls(obj)

Реконструктор (метод класса _reconstruct) возвращает объект, предоставляющий буфер, если он имеет правильный тип. Это простой способ имитировать поведение нулевой копии на этом игрушечном примере.

На стороне потребителя мы можем мариновать эти объекты обычным способом, что в несериализованном виде даст нам копию исходного объекта:

b = ZeroCopyByteArray(b"abc")
data = pickle.dumps(b, protocol=5)
new_b = pickle.loads(data)
print(b == new_b)  # True
print(b is new_b)  # False: a copy was made

Но если мы передадим buffer_callback, а затем вернем накопленные буферы при разсериализации, мы сможем получить обратно исходный объект:

b = ZeroCopyByteArray(b"abc")
buffers = []
data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append)
new_b = pickle.loads(data, buffers=buffers)
print(b == new_b)  # True
print(b is new_b)  # True: no copy was made

Этот пример ограничен тем фактом, что bytearray выделяет собственную память: вы не можете создать экземпляр bytearray, который будет опираться на память другого объекта. Однако сторонние типы данных, такие как массивы NumPy, не имеют этого ограничения и позволяют использовать zero-copy pickling (или создание как можно меньшего количества копий) при передаче между различными процессами или системами.

См.также

PEP 574 – Протокол Pickle 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):
        # 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)

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

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)

См.также

Модуль copyreg

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

Модуль pickletools

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

Модуль shelve

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

Модуль copy

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

Модуль marshal

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

Сноски