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 для чтения или
-, указывающий на чтение из стандартного ввода.
См. также
-
Modulecopyreg -
Регистрация конструкторов интерфейса pickle для типов расширения.
-
Modulepickletools -
Инструменты для работы с сериализованными данными и их анализа.
-
Moduleshelve -
Индексированные базы данных объектов; использует
pickle. -
Modulecopy -
Поверхностное и глубокое копирование объектов.
-
Modulemarshal -
Высокопроизводительная сериализация встроенных типов.
Сноски
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/pickle.html