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 существует в основном для поддержки файлов Python .pyc.

Модуль 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 или XDR (которые не могут представлять совместное использование указателей); однако это означает, что не-Python программы не смогут восстановить маринованные Python объекты.

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

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

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

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

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

  • Протокол версии 2 был представлен в Python 2.3. Он обеспечивает гораздо более эффективную выборку new-style classes. См. раздел PEP 307 для получения информации об улучшениях, внесенных протоколом 2.

  • Протокол версии 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 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: Был добавлен аргумент buffers.

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

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

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

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

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

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

exception pickle.PickleError

Общий базовый класс для других исключений пикинга. Он наследует Exception.

exception pickle.PicklingError

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

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

exception pickle.UnpicklingError

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

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

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

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

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

Необязательный аргумент protocol, целое число, указывает пиклеру на использование заданного протокола; поддерживаются протоколы от 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(), не может само иметь постоянный идентификатор.

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

dispatch_table

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

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

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

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

reducer_override(obj)

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

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

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

fast

Исправлено. Включает быстрый режим, если установлено в значение true. Быстрый режим отключает использование memo, что ускоряет процесс пикинга, не генерируя лишних опкодов 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 равен 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: Был добавлен аргумент buffers.

load()

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

persistent_load(pid)

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

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

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

find_class(module, name)

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

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

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

class pickle.PickleBuffer(buffer)

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

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

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

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

raw()

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

release()

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

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

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

  • None, True и False;

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

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

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

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

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

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

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

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

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

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

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

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

Экземпляры класса пикелевания

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

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

object.__getstate__()

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

object.__setstate__(state)

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

Примечание

Если __getstate__() возвращает значение false, метод __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() и extend() с соответствующей сигнатурой. (Используется ли append() или extend(), зависит от версии протокола pickle, а также от количества добавляемых элементов, поэтому оба метода должны поддерживаться).

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

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

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

object.__reduce_ex__(protocol)

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

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

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

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

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

Для распаковки внешних объектов распаковщик должен иметь пользовательский метод 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.

Работа с государственными объектами

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

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

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

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

Для таких случаев можно создать подкласс на основе класса Pickler и реализовать метод reducer_override(). Этот метод может возвращать произвольный редукционный кортеж (см. __reduce__()). В качестве альтернативы он может возвращать NotImplemented для возврата к традиционному поведению.

Если определены оба метода dispatch_table и reducer_override(), то приоритет имеет метод reducer_override().

Примечание

По соображениям производительности, reducer_override() не может быть вызван для следующих объектов: None, True, False и точных экземпляров int, float, bytes, str, dict, set, frozenset, list и tuple.

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

import io
import pickle

class MyClass:
    my_attribute = 1

class MyPickler(pickle.Pickler):
    def reducer_override(self, obj):
        """Custom reducer for MyClass."""
        if getattr(obj, "__name__", None) == "MyClass":
            return type, (obj.__name__, obj.__bases__,
                          {'my_attribute': obj.my_attribute})
        else:
            # For any other object, fallback to usual reduction
            return NotImplemented

f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)

del MyClass

unpickled_class = pickle.loads(f.getvalue())

assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1

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

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

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

От этого ограничения можно отказаться, если и провайдер (реализация передаваемых типов объектов), и потребитель (реализация системы связи) поддерживают средства внеполосной передачи, предоставляемые протоколом 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

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

Сноски

1

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

2

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

3

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

4

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

5

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

Вернуться на верх