pickle — сериализация объектов Python
Исходный код: Lib/pickle.py
Модуль pickle реализует двоичные протоколы для сериализации и десериализации структуры объектов Python. «Упаковка» — это процесс преобразования иерархии объектов Python в поток байтов, а «распаковка» — обратная операция, при которой поток байтов (из двоичного файла или объекта, подобного байтам) преобразуется обратно в иерархию объектов. Упаковку (и распаковку) также называют «сериализацией», «маршалингом»[1] или «уплощением»; однако во избежание путаницы здесь используются термины «упаковка» и «распаковка».
Предупреждение
Модуль pickle не является безопасным. Распаковывайте только те данные, которым доверяете.
Можно создать вредоносные данные pickle, которые выполнят произвольный код во время распаковки. Никогда не распаковывайте данные, которые могли поступить из ненадёжного источника или быть изменены.
Если необходимо убедиться, что данные не были изменены, рассмотрите возможность подписать их с помощью hmac.
Для обработки ненадёжных данных могут лучше подойти более безопасные форматы сериализации, например json. См. раздел Сравнение с json.
Связь с другими модулями Python
Сравнение с marshal
В Python есть более примитивный модуль сериализации под названием marshal, но в целом для сериализации объектов Python всегда следует предпочитать pickle. marshal существует главным образом для поддержки файлов .pyc в Python.
Модуль pickle отличается от marshal по нескольким важным параметрам:
-
Модуль
pickleотслеживает объекты, которые уже были сериализованы, поэтому последующие ссылки на тот же объект не сериализуются повторно.marshalэтого не делает.Это имеет значение как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты — это объекты, содержащие ссылки на самих себя. marshal не обрабатывает такие объекты; более того, попытка маршалинга рекурсивных объектов приведёт к аварийному завершению интерпретатора Python. Совместное использование объектов возникает, когда на один и тот же объект в разных местах сериализуемой иерархии объектов ссылаются несколько раз.
pickleсохраняет такие объекты только один раз и гарантирует, что все остальные ссылки указывают на основной экземпляр. Совместно используемые объекты остаются общими, что может быть очень важно для изменяемых объектов. -
marshalнельзя использовать для сериализации классов, определённых пользователем, и их экземпляров.pickleможет прозрачно сохранять и восстанавливать экземпляры классов, однако определение класса должно быть доступно для импорта и находиться в том же модуле, что и при сохранении объекта. - Формат сериализации
marshalне гарантированно переносим между версиями Python. Поскольку его основное назначение — поддержка файлов.pyc, разработчики Python оставляют за собой право при необходимости изменять формат сериализации способами, несовместимыми с предыдущими версиями. Для формата сериализацииpickleгарантируется обратная совместимость между выпусками Python, если выбран совместимый протокол pickle, а код упаковки и распаковки учитывает различия типов между Python 2 и Python 3, если данные пересекают эту уникальную границу несовместимости языка.
Сравнение с json
Между протоколами pickle и JSON (JavaScript Object Notation — нотация объектов JavaScript) существуют принципиальные различия:
- JSON — текстовый формат сериализации (он выдаёт текст в кодировке Unicode, хотя большую часть времени затем этот текст кодируется в
utf-8), а pickle — двоичный формат сериализации; - JSON удобен для чтения, а pickle — нет;
- JSON совместим с другими системами и широко используется за пределами экосистемы Python, а pickle предназначен только для Python;
- По умолчанию JSON может представлять только подмножество встроенных типов Python и не поддерживает пользовательские классы; pickle может представлять чрезвычайно большое количество типов Python (многие из них автоматически благодаря умелому использованию средств интроспекции Python; сложные случаи можно обработать, реализовав специальные API объектов);
- В отличие от pickle, десериализация ненадёжного JSON сама по себе не создаёт уязвимости, связанной с выполнением произвольного кода.
См. также
Модуль json: модуль стандартной библиотеки, обеспечивающий сериализацию и десериализацию JSON.
Формат потока данных
Формат данных, используемый pickle, предназначен только для Python. Это означает, что на него не распространяются ограничения внешних стандартов, таких как JSON или XDR (которые не могут представлять совместное использование указателей); однако программы, не написанные на Python, могут оказаться неспособны восстановить упакованные объекты Python.
По умолчанию формат данных pickle использует относительно компактное двоичное представление. Если требуется добиться оптимального размера, упакованные данные можно эффективно сжать.
Модуль pickletools содержит инструменты для анализа потоков данных, созданных pickle. Исходный код pickletools содержит подробные комментарии об опкодах, используемых протоколами pickle.
В настоящее время для упаковки можно использовать 6 различных протоколов. Чем выше версия используемого протокола, тем более новая версия Python потребуется для чтения созданного pickle.
- Протокол версии 0 — это исходный протокол, «удобный для чтения человеком»; он обратно совместим с более ранними версиями Python.
- Протокол версии 1 — это старый двоичный формат, также совместимый с более ранними версиями Python.
- Протокол версии 2 появился в Python 2.3. Он обеспечивает гораздо более эффективную упаковку классов нового стиля. Сведения об улучшениях, появившихся в протоколе 2, см. в PEP 307.
- Протокол версии 3 появился в Python 3.0. Он добавил явную поддержку объектов
bytes, и Python 2.x не может распаковывать данные, созданные с его помощью. Этот протокол использовался по умолчанию в Python 3.0–3.7. - Протокол версии 4 появился в Python 3.4. Он добавил поддержку очень больших объектов, упаковку большего количества типов объектов и некоторые оптимизации формата данных. Начиная с Python 3.8 он используется по умолчанию. Сведения об улучшениях, появившихся в протоколе 4, см. в PEP 3154.
- Протокол версии 5 появился в Python 3.8. Он добавил поддержку внешних данных и ускорение обработки встроенных данных. Сведения об улучшениях, появившихся в протоколе 5, см. в PEP 574.
Примечание
Сериализация — более примитивное понятие, чем постоянное хранение; хотя pickle считывает и записывает файловые объекты, она не решает задачу присвоения имён постоянно хранимым объектам и тем более не решает (ещё более сложную) задачу одновременного доступа к постоянно хранимым объектам. Модуль pickle может преобразовать сложный объект в поток байтов, а затем восстановить из потока байтов объект с такой же внутренней структурой. Наиболее очевидное применение таких потоков байтов — запись в файл, но их также можно отправлять по сети или хранить в базе данных. Модуль shelve предоставляет простой интерфейс для упаковки и распаковки объектов в файлах баз данных в стиле DBM.
Интерфейс модуля
Чтобы сериализовать иерархию объектов, достаточно вызвать функцию dumps(). Аналогично, чтобы десериализовать поток данных, вызовите функцию loads(). Однако, если вам нужен больший контроль над сериализацией и десериализацией, можно создать соответственно объект Pickler или Unpickler.
Модуль pickle предоставляет следующие константы:
-
pickle.HIGHEST_PROTOCOL -
Целое число, обозначающее наивысшую доступную версию протокола. Это значение можно передать в качестве значения protocol функциям
dump()иdumps(), а также конструкторуPickler.
-
pickle.DEFAULT_PROTOCOL -
Целое число, обозначающее версию протокола, используемую по умолчанию для сериализации с помощью pickle. Может быть меньше значения
HIGHEST_PROTOCOL. В настоящее время протоколом по умолчанию является версия 4, впервые представленная в Python 3.4 и несовместимая с предыдущими версиями.Изменено в версии 3.0: Протоколом по умолчанию является версия 3.
Изменено в версии 3.8: Протоколом по умолчанию является версия 4.
Модуль pickle предоставляет следующие функции для упрощения процесса сериализации с помощью pickle:
-
pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None) -
Записывает сериализованное представление объекта obj в открытый файловый объект file. Это эквивалентно
Pickler(file, protocol).dump(obj).Аргументы file, protocol, fix_imports и buffer_callback имеют то же значение, что и в конструкторе
Pickler.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
-
pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None) -
Возвращает сериализованное представление объекта obj в виде объекта
bytes, а не записывает его в файл.Аргументы protocol, fix_imports и buffer_callback имеют то же значение, что и в конструкторе
Pickler.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
-
pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Считывает сериализованное представление объекта из открытого файлового объекта file и возвращает восстановленную иерархию объектов, заданную в этом представлении. Это эквивалентно
Unpickler(file).load().Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется. Байты, следующие за сериализованным представлением объекта, игнорируются.
Аргументы file, fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе
Unpickler.Изменено в версии 3.8: Добавлен аргумент buffers.
-
pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Возвращает восстановленную иерархию объектов из сериализованного представления объекта data. data должен быть объектом, подобным bytes.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется. Байты, следующие за сериализованным представлением объекта, игнорируются.
Аргументы fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе
Unpickler.Изменено в версии 3.8: Добавлен аргумент buffers.
Модуль pickle определяет три исключения:
-
exception pickle.PickleError -
Общий базовый класс для остальных исключений, связанных с pickle. Он наследуется от
Exception.
-
exception pickle.PicklingError -
Ошибка, возникающая, когда
Picklerвстречает объект, который нельзя сериализовать с помощью pickle. Она наследуется отPickleError.Сведения о том, какие объекты можно сериализовать с помощью pickle, см. в разделе Что можно сериализовать и десериализовать с помощью pickle?.
-
exception pickle.UnpicklingError -
Ошибка, возникающая при проблемах с десериализацией объекта, например из-за повреждения данных или нарушения безопасности. Она наследуется от
PickleError.Обратите внимание, что при десериализации также могут возникать другие исключения, включая, помимо прочих, AttributeError, EOFError, ImportError и IndexError.
Модуль pickle экспортирует три класса: Pickler, Unpickler и PickleBuffer:
-
class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None) -
Принимает двоичный файл для записи потока данных pickle.
Необязательный аргумент protocol — целое число, указывающее протокол, который должен использовать сериализатор; поддерживаются протоколы от 0 до
HIGHEST_PROTOCOL. Если аргумент не указан, используется значениеDEFAULT_PROTOCOL. Если указано отрицательное число, выбираетсяHIGHEST_PROTOCOL.Аргумент file должен иметь метод write(), принимающий один аргумент типа bytes. Таким образом, им может быть файл на диске, открытый для двоичной записи, экземпляр
io.BytesIOили любой другой пользовательский объект, соответствующий этому интерфейсу.Если fix_imports имеет значение true, а protocol меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных pickle можно было прочитать в Python 2.
Если buffer_callback имеет значение
None(по умолчанию), представления буферов сериализуются в file как часть потока pickle.Если buffer_callback не имеет значение
None, его можно вызывать любое количество раз, передавая ему представление буфера. Если обратный вызов возвращает ложное значение (например,None), указанный буфер передаётся вне основного потока; в противном случае буфер сериализуется в основном потоке, то есть внутри потока pickle.Если buffer_callback не имеет значение
None, а protocol имеет значениеNoneили меньше 5, возникает ошибка.Изменено в версии 3.8: Добавлен аргумент buffer_callback.
-
dump(obj) -
Записывает сериализованное представление obj в открытый файловый объект, переданный конструктору.
-
persistent_id(obj) -
По умолчанию ничего не делает. Этот метод предназначен для переопределения в подклассе.
Если
persistent_id()возвращаетNone, obj сериализуется с помощью pickle обычным образом. Любое другое значение приводит к тому, чтоPicklerвыдаёт возвращённое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должен определять методUnpickler.persistent_load(). Обратите внимание, что возвращаемоеpersistent_id()значение само не может иметь постоянный идентификатор.Подробности и примеры использования см. в разделе Сохранение внешних объектов.
-
dispatch_table -
Таблица диспетчеризации объекта-сериализатора — это реестр функций редукции, которые можно объявить с помощью
copyreg.pickle(). Это отображение, в котором ключами являются классы, а значениями — функции редукции. Функция редукции принимает один аргумент соответствующего класса и должна соответствовать тому же интерфейсу, что и метод__reduce__().По умолчанию у объекта-сериализатора нет атрибута
dispatch_table; вместо него используется глобальная таблица диспетчеризации, которой управляет модульcopyreg. Однако, чтобы настроить сериализацию с помощью pickle для конкретного объекта-сериализатора, можно присвоить атрибутуdispatch_tableобъект, подобный словарю. Кроме того, если у подкласса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().
-
-
class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None) -
Принимает двоичный файл для чтения потока данных pickle.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется.
Аргумент file должен иметь три метода: read(), принимающий целочисленный аргумент; readinto(), принимающий аргумент-буфер; и readline(), не принимающий аргументов, как в интерфейсе
io.BufferedIOBase. Таким образом, file может быть файлом на диске, открытым для двоичного чтения, объектомio.BytesIOили любым другим пользовательским объектом, соответствующим этому интерфейсу.Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости с потоками pickle, созданными в Python 2. Если fix_imports имеет значение true, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. Аргументы encoding и errors задают способ декодирования 8-битных строк, сериализованных с помощью pickle в Python 2; по умолчанию используются значения ‘ASCII’ и ‘strict’ соответственно. Для чтения таких 8-битных строк как объектов bytes аргументу encoding можно присвоить значение ‘bytes’. Для десериализации массивов NumPy, а также экземпляров
datetime,dateиtime, сериализованных с помощью pickle в Python 2, необходимо использоватьencoding='latin1'.Если buffers имеет значение
None(по умолчанию), все данные, необходимые для десериализации, должны содержаться в потоке pickle. Это означает, что при созданииPicklerаргумент buffer_callback имел значениеNone(или это значение использовалось при вызовеdump()илиdumps()).Если buffers не имеет значение
None, он должен быть итерируемым объектом из объектов, поддерживающих буферы; он используется каждый раз, когда поток pickle ссылается на представление буфера, переданного вне основного потока. Такие буферы в заданном порядке передаются аргументу buffer_callback объекта Pickler.Изменено в версии 3.8: Добавлен аргумент buffers.
-
load() -
Считывает сериализованное представление объекта из открытого файлового объекта, переданного конструктору, и возвращает заданную в нём восстановленную иерархию объектов. Байты, следующие за сериализованным представлением объекта, игнорируются.
-
persistent_load(pid) -
По умолчанию вызывает исключение
UnpicklingError.Если определён,
persistent_load()должен возвращать объект, указанный постоянным идентификатором pid. При обнаружении недопустимого постоянного идентификатора должно быть вызвано исключениеUnpicklingError.Подробности и примеры использования см. в разделе Сохранение внешних объектов.
-
find_class(module, name) -
При необходимости импортирует module и возвращает объект с именем name из этого модуля; аргументы module и name являются объектами
str. Обратите внимание: несмотря на название,find_class()используется также для поиска функций.Подклассы могут переопределить этот метод, чтобы контролировать, какие типы объектов и каким образом могут загружаться, потенциально снижая риски безопасности. Подробности см. в разделе Ограничение глобальных объектов.
Вызывает событие аудита
pickle.find_classс аргументамиmodule,name.
-
-
class pickle.PickleBuffer(buffer) -
Обёртка для буфера, представляющего данные, которые можно сериализовать с помощью pickle. buffer должен быть объектом, предоставляющим буфер, например объектом, подобным bytes, или N-мерным массивом.
PickleBufferсам является объектом, предоставляющим буфер, поэтому его можно передавать другим API, ожидающим объект такого типа, напримерmemoryview.Объекты
PickleBufferможно сериализовать только с помощью протокола pickle версии 5 или выше. Их можно использовать для сериализации вне основного потока.Добавлено в версии 3.8.
-
raw() -
Возвращает объект
memoryviewдля области памяти, лежащей в основе этого буфера. Возвращаемый объект — это одномерное представление памяти с непрерывным расположением в памяти в формате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, необходимо определять на верхнем уровне модуля.
Аналогично, при сериализации экземпляров классов их код и данные класса не сериализуются вместе с ними. Сериализуются только данные экземпляра. Это сделано намеренно: так можно исправлять ошибки в классе или добавлять в него методы и при этом загружать объекты, созданные с помощью предыдущей версии класса. Если предполагается, что объекты будут существовать долго и за это время класс претерпит множество изменений, имеет смысл включить в объекты номер версии, чтобы метод класса __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 выполняет поиск в пространстве имён модуля, чтобы определить модуль объекта. Такое поведение обычно полезно для одиночек.
Если возвращается кортеж, он должен содержать от двух до шести элементов. Необязательные элементы можно опустить или указать для них
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)
В следующем примере считываются полученные данные pickle.
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
См. также
-
Modulecopyreg -
Регистрация конструкторов интерфейса pickle для типов расширения.
-
Modulepickletools -
Инструменты для работы с данными pickle и их анализа.
-
Moduleshelve -
Индексированные базы данных объектов; использует
pickle. -
Modulecopy -
Поверхностное и глубокое копирование объектов.
-
Modulemarshal -
Высокопроизводительная сериализация встроенных типов.
Сноски
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/pickle.html