Spec-Zone.ru › Python 3.14

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

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

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

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

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

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

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

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

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

Сравнение с marshal

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

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

  • 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 (который не может представлять совместное использование указателей); однако это означает, что программы, написанные не на 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–3.13. Информацию об улучшениях, внесённых протоколом 4, см. в документе PEP 3154.
  • Версия протокола 5 появилась в Python 3.8. Она добавила поддержку внеполосных данных и ускорение обработки внутриполосных данных. Начиная с Python 3.14, это протокол по умолчанию. Информацию об улучшениях, внесённых протоколом 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. В настоящее время протоколом по умолчанию является версия 5, добавленная в Python 3.8 и несовместимая с предыдущими версиями. В этой версии появилась поддержка внеполосных буферов, позволяющая передавать данные, совместимые с PEP 3118, отдельно от основного потока pickle.

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

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

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

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

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

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

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

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

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

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

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

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

exception pickle.PickleError

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

exception pickle.PicklingError

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

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

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

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

dispatch_table

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

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

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

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

reducer_override(obj)

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

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

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

fast

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

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

clear_memo()

Очищает «memo» сериализатора pickle.

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

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

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

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

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

Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости с потоками pickle, созданными Python 2. Если fix_imports имеет значение true, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Аргументы encoding и errors указывают pickle, как декодировать экземпляры 8-битных строк, сериализованные в Python 2; по умолчанию это значения ‘ASCII’ и ‘strict’ соответственно. В качестве значения encoding можно указать ‘bytes’, чтобы читать эти экземпляры 8-битных строк как объекты bytes. Для десериализации массивов 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);
  • целые числа, числа с плавающей точкой, комплексные числа;
  • строки, байты, массивы байтов;
  • кортежи, списки, множества и словари, содержащие только объекты, пригодные для сериализации с помощью 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: В классе object добавлена реализация метода __getstate__() по умолчанию.

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 ищет объект в пространстве имён модуля, чтобы определить его модуль: для сериализуемого obj атрибут __module__ ищется непосредственно у obj; если экземплярный атрибут __module__ не задан, поиск выполняется у типа obj. Такое поведение обычно полезно для одиночек.

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

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

    Добавлено в версии 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)

Интерфейс командной строки

Модуль pickle можно запустить как скрипт из командной строки — он отобразит содержимое pickle-файлов. Однако если файл pickle, который вы хотите проверить, получен из ненадёжного источника, -m pickletools — более безопасный вариант, поскольку он не выполняет байт-код pickle. См. раздел Использование CLI pickletools.

python -m pickle pickle_file [pickle_file ...]

Допускается следующий параметр:

pickle_file

Файл pickle для чтения или -, указывающий на чтение из стандартного ввода.

См. также

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

Spec-Zone.ru

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