Spec-Zone.ru › Python 3.13

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

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

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

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

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

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

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

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

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

Сравнение с marshal

В Python есть более примитивный модуль сериализации под названием marshal, но в целом для сериализации объектов Python всегда предпочтительно использовать pickle. Модуль marshal существует главным образом для поддержки файлов .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) есть принципиальные различия:

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

См. также

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

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

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

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

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

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

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

Примечание

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

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

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

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

pickle.HIGHEST_PROTOCOL

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

pickle.DEFAULT_PROTOCOL

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

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

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

Модуль pickle предоставляет следующие функции, упрощающие процесс сериализации с помощью 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

Общий базовый класс для остальных исключений, связанных с pickle. Наследуется от Exception.

exception pickle.PicklingError

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

Сведения о том, какие объекты можно сериализовать с помощью pickle, см. в разделе Какие объекты можно сериализовать и десериализовать с помощью pickle?.

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 — целое число, указывающее сериализатору pickle использовать заданный протокол; поддерживаются протоколы от 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 сериализуется с помощью pickle обычным образом. Любое другое значение приводит к тому, что Pickler выдаёт возвращённое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно определяться методом Unpickler.persistent_load(). Обратите внимание, что значение, возвращаемое persistent_id(), само не может иметь постоянный идентификатор.

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

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

dispatch_table

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

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

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

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

reducer_override(obj)

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

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

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

fast

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

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

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

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

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

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

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

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

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

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

load()

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

persistent_load(pid)

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

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

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

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

find_class(module, name)

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

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

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

class pickle.PickleBuffer(buffer)

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

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

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

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

raw()

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

release()

Освободить базовый буфер, предоставляемый объектом PickleBuffer.

Какие объекты можно сериализовать и десериализовать с помощью pickle?

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

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

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

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

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

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

Именно поэтому сериализуемые с помощью pickle функции и классы должны быть определены на верхнем уровне модуля.

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

object.__getstate__()

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

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

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

object.__setstate__(state)

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

Примечание

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

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

Примечание

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

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

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

object.__reduce__()

В настоящее время интерфейс определён следующим образом. Метод __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__() автоматически становится синонимом расширенной версии. Основное назначение этого метода — предоставление обратно совместимых значений 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 с данными вне основного потока

Ограничение глобальных объектов

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

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

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

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

Ниже приведён пример десериализатора, который разрешает загружать лишь несколько безопасных классов из модуля builtins:

import builtins
import io
import pickle

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

class RestrictedUnpickler(pickle.Unpickler):

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

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

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

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

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

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

В последних версиях протокола pickle (начиная с протокола 2) предусмотрены эффективные двоичные представления для ряда распространённых возможностей и встроенных типов. Кроме того, модуль pickle содержит прозрачный оптимизатор, написанный на C.

Примеры

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

import pickle

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

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

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

import pickle

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

См. также

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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/pickle.html

Spec-Zone.ru

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