Spec-Zone.ru › Python 3.12

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-like object.

Версия протокола 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, целое число, сообщает pickler использовать заданный протокол; поддерживаемые протоколы — от 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

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

По умолчанию у объекта pickler не будет атрибута dispatch_table, и он будет использовать глобальную таблицу диспетчеризации, управляемую модулем copyreg. Однако для настройки сериализации для конкретного объекта pickler можно установить атрибут dispatch_table в объект типа dict. Кроме того, если подкласс 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(), если вам нужны более компактные 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 имеет значение True, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. encoding и errors сообщают pickle, как декодировать экземпляры строк 8-битных байтов, закодированные Python 2; по умолчанию они равны ‘ASCII’ и ‘strict’ соответственно. encoding может быть ‘bytes’ для чтения этих экземпляров строк 8-битных байтов как объектов bytes. Использование encoding='latin1' необходимо для распаковки массивов NumPy и экземпляров datetime, date и time, закодированных Python 2.

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

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

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

load()

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

persistent_load(pid)

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

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

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

find_class(module, name)

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

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

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

class pickle.PickleBuffer(buffer)

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

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

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

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

raw()

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

release()

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

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

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

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

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

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

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

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

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

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

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

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

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

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

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

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

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

object.__setstate__(state)

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

Примечание

Если __reduce__() возвращает состояние со значением None во время сериализации, метод __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 __setstate__(). Если он не None, этот вызываемый объект будет иметь приоритет над статическим методом obj __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 поставщика

Объекты с большими данными, которые нужно закодировать с помощью pickle, должны реализовывать метод __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 с данными вне канала

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

По умолчанию, декодирование pickle импортирует любые классы или функции, которые находит в данных 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)

Следующий пример считывает полученные данные в формате 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

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

Module shelve

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

Module copy

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

Module marshal

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

Примечания

[1]

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

[2]

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

[3]

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

[4]

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

[5]

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

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

Spec-Zone.ru

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