Spec-Zone.ru › Python 3.9

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

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

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

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

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

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

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

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

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

Сравнение с marshal

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

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

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

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

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

Сравнение с json

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

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

См. также

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

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

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

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

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

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

  • Протокол версии 0 — это оригинальный «человекочитаемый» протокол и обратно совместим с более ранними версиями Python.
  • Протокол версии 1 — это старый двоичный формат, также совместимый с более ранними версиями Python.
  • Протокол версии 2 был представлен в Python 2.3. Он обеспечивает гораздо более эффективное кодирование классов нового стиля. Обратитесь к 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 функциям dump() и dumps(), а также конструктору Pickler.

pickle.DEFAULT_PROTOCOL

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Аргументы 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(), принимающий один аргумент типа bytes. Таким образом, он может быть файлом на диске, открытым для двоичного записи, экземпляром io.BytesIO или любым другим пользовательским объектом, соответствующим этому интерфейсу.

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

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

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

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

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

dump(obj)

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

persistent_id(obj)

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

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

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

dispatch_table

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

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

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

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

reducer_override(obj)

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

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

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

fast

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

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

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

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

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

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

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

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

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

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

load()

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

persistent_load(pid)

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

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

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

find_class(module, name)

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

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

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

class pickle.PickleBuffer(buffer)

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

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

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

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

raw()

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

release()

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

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

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

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

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

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

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

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

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

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

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

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

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

object.__getnewargs_ex__()

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

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

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

object.__getnewargs__()

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

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

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

object.__getstate__()

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

object.__setstate__(state)

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

Примечание

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

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

Примечание

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

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

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

object.__reduce__()

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

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

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

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

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

object.__reduce_ex__(protocol)

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

Сохранение внешних объектов

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

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

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

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

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

# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.

import pickle
import sqlite3
from collections import namedtuple

# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")

class DBPickler(pickle.Pickler):

    def persistent_id(self, obj):
        # Instead of pickling MemoRecord as a regular class instance, we emit a
        # persistent ID.
        if isinstance(obj, MemoRecord):
            # Here, our persistent ID is simply a tuple, containing a tag and a
            # key, which refers to a specific record in the database.
            return ("MemoRecord", obj.key)
        else:
            # If obj does not have a persistent ID, return None. This means obj
            # needs to be pickled as usual.
            return None


class DBUnpickler(pickle.Unpickler):

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

    def persistent_load(self, pid):
        # This method is invoked whenever a persistent ID is encountered.
        # Here, pid is the tuple returned by DBPickler.
        cursor = self.connection.cursor()
        type_tag, key_id = pid
        if type_tag == "MemoRecord":
            # Fetch the referenced record from the database and return it.
            cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
            key, task = cursor.fetchone()
            return MemoRecord(key, task)
        else:
            # Always raises an error if you cannot return the correct object.
            # Otherwise, the unpickler will think None is the object referenced
            # by the persistent ID.
            raise pickle.UnpicklingError("unsupported persistent object")


def main():
    import io
    import pprint

    # Initialize and populate our database.
    conn = sqlite3.connect(":memory:")
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
    tasks = (
        'give food to fish',
        'prepare group meeting',
        'fight with a zebra',
        )
    for task in tasks:
        cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))

    # Fetch the records to be pickled.
    cursor.execute("SELECT * FROM memos")
    memos = [MemoRecord(key, task) for key, task in cursor]
    # Save the records using our custom DBPickler.
    file = io.BytesIO()
    DBPickler(file).dump(memos)

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

    # Update a record, just for good measure.
    cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")

    # Load the records from the pickle data stream.
    file.seek(0)
    memos = DBUnpickler(file, conn).load()

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


if __name__ == '__main__':
    main()

Таблицы перенаправления

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

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

Например

f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass

создаёт экземпляр pickle.Pickler с частной таблицей перенаправления, которая обрабатывает класс SomeClass особым образом. В качестве альтернативы, код

class MyPickler(pickle.Pickler):
    dispatch_table = copyreg.dispatch_table.copy()
    dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)

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

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

модифицирует глобальную таблицу перенаправления, используемую всеми пользователями модуля copyreg.

Обработка состоятельных объектов

Вот пример, показывающий, как изменить поведение сериализации для класса. Класс TextReader открывает текстовый файл и каждый раз при вызове метода readline() возвращает номер строки и содержимое строки. Если экземпляр TextReader сериализуется, сохраняются все атрибуты, *кроме* члена объекта файла. При десериализации экземпляра файл повторно открывается, и чтение возобновляется с последней позиции. Для реализации этого поведения используются методы __setstate__() и __getstate__().

class TextReader:
    """Print and number lines in a text file."""

    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename)
        self.lineno = 0

    def readline(self):
        self.lineno += 1
        line = self.file.readline()
        if not line:
            return None
        if line.endswith('\n'):
            line = line[:-1]
        return "%i: %s" % (self.lineno, line)

    def __getstate__(self):
        # Copy the object's state from self.__dict__ which contains
        # all our instance attributes. Always use the dict.copy()
        # method to avoid modifying the original state.
        state = self.__dict__.copy()
        # Remove the unpicklable entries.
        del state['file']
        return state

    def __setstate__(self, state):
        # Restore instance attributes (i.e., filename and lineno).
        self.__dict__.update(state)
        # Restore the previously opened file's state. To do so, we need to
        # reopen it and read from it until the line count is restored.
        file = open(self.filename)
        for _ in range(self.lineno):
            file.readline()
        # Finally, save the file.
        self.file = file

Пример использования может быть примерно таким:

>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'

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

Новое в версии 3.8.

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

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

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

Примечание

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

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

import io
import pickle

class MyClass:
    my_attribute = 1

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

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

del MyClass

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

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

Буферы вне зоны обслуживания

Новое в версии 3.8.

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

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

См. также

PEP 574 – Протокол pickle 5 с данными вне зоны обслуживания

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

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

>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0

В этом примере десериализатор импортирует функцию os.system() и затем применяет строковый аргумент «echo hello world». Хотя этот пример безобиден, несложно представить такой, который мог бы навредить вашей системе.

По этой причине вы можете захотеть контролировать, что десериализуется, настроив Unpickler.find_class(). Вопреки своему названию, Unpickler.find_class() вызывается всякий раз, когда запрашивается глобальная переменная (то есть класс или функция). Таким образом, вы можете либо полностью запретить глобальные переменные, либо ограничить их до безопасного подмножества.

Вот пример десериализатора, позволяющего загрузить только несколько безопасных классов из модуля builtins:

import builtins
import io
import pickle

safe_builtins = {
    'range',
    'complex',
    'set',
    'frozenset',
    'slice',
}

class RestrictedUnpickler(pickle.Unpickler):

    def find_class(self, module, name):
        # Only allow safe classes from builtins.
        if module == "builtins" and name in safe_builtins:
            return getattr(builtins, name)
        # Forbid everything else.
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
                                     (module, name))

def restricted_loads(s):
    """Helper function analogous to pickle.loads()."""
    return RestrictedUnpickler(io.BytesIO(s)).load()

Пример использования нашего десериализатора, работающего как ожидалось:

>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
  ...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
...                  b'(S\'getattr(__import__("os"), "system")'
...                  b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
  ...
pickle.UnpicklingError: global 'builtins.eval' is forbidden

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

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

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

Примеры

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

import pickle

# An arbitrary collection of objects supported by pickle.
data = {
    'a': [1, 2.0, 3+4j],
    'b': ("character string", b"byte string"),
    'c': {None, True, False}
}

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

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

import pickle

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

См. также

Module copyreg

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

Module pickletools

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

Module shelve

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

Module copy

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

Module marshal

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

Примечания

1

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

2

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

3

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

4

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

5

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

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

Spec-Zone.ru

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