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) существуют принципиальные различия:
- 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 определяется автоматически, поэтому указывать аргумент 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, см. в разделе Что можно сериализовать и десериализовать с помощью 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 сериализуется обычным образом. Любое другое значение приводит к тому, чтоPicklerвыдаёт возвращённое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должен определять методUnpickler.persistent_load(). Обратите внимание, что возвращаемое значениеpersistent_id()само не может иметь постоянный идентификатор.Подробности и примеры использования см. в разделе Сохранение внешних объектов.
-
dispatch_table -
Таблица диспетчеризации объекта-сериализатора — это реестр функций редукции, которые можно объявить с помощью
copyreg.pickle(). Это отображение, в котором ключами являются классы, а значениями — функции редукции. Функция редукции принимает один аргумент соответствующего класса и должна соответствовать тому же интерфейсу, что и метод__reduce__().По умолчанию у объекта-сериализатора нет атрибута
dispatch_table; вместо этого он использует глобальную таблицу диспетчеризации, которой управляет модульcopyreg. Чтобы настроить сериализацию с помощью pickle для конкретного объекта-сериализатора, можно присвоить атрибутуdispatch_tableобъект, подобный dict. Кроме того, если у подклассаPicklerесть атрибутdispatch_table, он будет использоваться как таблица диспетчеризации по умолчанию для экземпляров этого класса.Примеры использования см. в разделе Таблицы диспетчеризации.
Добавлено в версии 3.3.
-
reducer_override(obj) -
Специальная функция редукции, которую можно определить в подклассах
Pickler. Этот метод имеет приоритет над любой функцией редукции вdispatch_table. Он должен соответствовать тому же интерфейсу, что и метод__reduce__(), и может при необходимости возвращатьNotImplemented, чтобы передать управление зарегистрированным вdispatch_tableфункциям редукции при сериализации с помощью pickleobj.Подробный пример см. в разделе Пользовательская редукция типов, функций и других объектов.
Добавлено в версии 3.8.
-
fast -
Устарело. Включает быстрый режим, если атрибуту присвоено истинное значение. Быстрый режим отключает использование memo, поэтому сериализация выполняется быстрее, поскольку не создаются избыточные опкоды PUT. Не следует использовать его с объектами, ссылающимися на самих себя: в противном случае
Picklerбудет рекурсивно вызываться бесконечно.Если вам нужны более компактные данные pickle, используйте
pickletools.optimize().
-
-
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-битные строковые экземпляры, сериализованные с помощью pickle в Python 2; по умолчанию им присвоены значения ‘ASCII’ и ‘strict’ соответственно. Для чтения этих 8-битных строковых экземпляров в виде объектов bytes аргументу encoding можно присвоить значение ‘bytes’. Для десериализации массивов NumPy и экземпляров
datetime,dateиtime, сериализованных с помощью pickle в Python 2, необходимо использоватьencoding='latin1'.Если buffers равен None (значение по умолчанию), все данные, необходимые для десериализации, должны содержаться в потоке pickle. Это означает, что аргумент buffer_callback был равен None при создании экземпляра
Pickler(или при вызовеdump()либоdumps()).Если buffers не равен None, он должен быть итерируемым объектом, содержащим объекты с поддержкой буферов. Такой объект используется каждый раз, когда в потоке pickle встречается ссылка на внеполосное представление буфера. Эти буферы по порядку передаются в buffer_callback объекта Pickler.
Изменено в версии 3.8: Добавлен аргумент buffers.
-
load() -
Читает сериализованное представление объекта из открытого файлового объекта, заданного в конструкторе, и возвращает указанную в нём восстановленную иерархию объектов. Байты, следующие за сериализованным представлением объекта, игнорируются.
-
persistent_load(pid) -
По умолчанию вызывает исключение
UnpicklingError.Если метод определён,
persistent_load()должен возвращать объект, указанный постоянным идентификатором pid. При обнаружении недопустимого постоянного идентификатора следует вызвать исключениеUnpicklingError.Подробности и примеры использования см. в разделе Сохранение внешних объектов.
-
find_class(module, name) -
При необходимости импортирует module и возвращает объект с именем name из этого модуля; аргументы module и name являются объектами
str. Обратите внимание: вопреки названию,find_class()также используется для поиска функций.Подклассы могут переопределить этот метод, чтобы контролировать типы загружаемых объектов и способ их загрузки, потенциально снижая риски для безопасности. Подробности см. в разделе Ограничение глобальных объектов.
Вызывает событие аудита
pickle.find_classс аргументамиmodule,name.
-
-
class pickle.PickleBuffer(buffer) -
Обёртка для буфера, представляющего данные, которые можно сериализовать с помощью 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, вызывает исключение PicklingError; в этом случае в базовый файл уже могло быть записано некоторое неопределённое количество байтов. Попытка сериализовать глубоко рекурсивную структуру данных может превысить максимальную глубину рекурсии; в этом случае вызывается исключение 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: Добавлена реализация метода
__getstate__()по умолчанию в классеobject. - Для класса без экземплярного
-
object.__setstate__(state) -
При десериализации, если класс определяет
__setstate__(), этот метод вызывается с десериализованным состоянием. В этом случае объект состояния не обязательно должен быть словарём. В противном случае сериализованное состояние должно быть словарём, а его элементы присваиваются словарю нового экземпляра.Примечание
Если при сериализации
__reduce__()возвращает состояние со значениемNone, метод__setstate__()при десериализации вызван не будет.
Дополнительные сведения об использовании методов __getstate__() и __setstate__() см. в разделе Обработка объектов с состоянием.
Примечание
При десериализации для экземпляра могут вызываться некоторые методы, например __getattr__(), __getattribute__() или __setattr__(). Если работа этих методов зависит от соблюдения некоторого внутреннего инварианта, тип должен реализовать __new__(), чтобы обеспечить соблюдение этого инварианта, поскольку при десериализации экземпляра __init__() не вызывается.
Как мы увидим далее, pickle не использует напрямую описанные выше методы. На самом деле эти методы являются частью протокола копирования, реализующего специальный метод __reduce__(). Протокол копирования предоставляет единый интерфейс для получения данных, необходимых для сериализации и копирования объектов. [4]
Несмотря на широкие возможности, непосредственная реализация __reduce__() в ваших классах чревата ошибками. Поэтому разработчикам классов следует по возможности использовать высокоуровневый интерфейс (то есть __getnewargs_ex__(), __getstate__() и __setstate__()). Однако мы рассмотрим случаи, когда использование __reduce__() — единственный вариант или позволяет повысить эффективность сериализации, либо и то и другое.
-
object.__reduce__() -
В настоящее время интерфейс определён следующим образом. Метод
__reduce__()не принимает аргументов и должен возвращать либо строку, либо, что предпочтительнее, кортеж (возвращаемый объект часто называют «значением свёртки»).Если возвращается строка, её следует интерпретировать как имя глобальной переменной. Это должно быть локальное имя объекта относительно его модуля; модуль pickle ищет объект в пространстве имён модуля, чтобы определить его модуль. Такое поведение обычно полезно для синглтонов.
Если возвращается кортеж, он должен содержать от двух до шести элементов. Необязательные элементы можно опустить или указать для них значение
None. Значение каждого элемента по порядку:- Вызываемый объект, который будет вызван для создания начальной версии объекта.
- Кортеж аргументов для вызываемого объекта. Если вызываемый объект не принимает аргументов, необходимо указать пустой кортеж.
- Необязательное состояние объекта, которое будет передано методу
__setstate__()объекта, как описано выше. Если у объекта нет такого метода, значение должно быть словарём, который будет добавлен к атрибуту__dict__объекта. - Необязательный итератор (не последовательность), выдающий последовательные элементы. Эти элементы будут добавлены к объекту по одному с помощью
obj.append(item)или пакетно с помощьюobj.extend(list_of_items). Это используется главным образом для подклассов списков, но может применяться и для других классов, если у них есть методы append и extend с подходящей сигнатурой. (Выбор междуappend()иextend()зависит от версии протокола pickle, а также от количества добавляемых элементов, поэтому должны поддерживаться оба варианта.) - Необязательный итератор (не последовательность), выдающий последовательные пары «ключ — значение». Эти элементы будут сохранены в объекте с помощью
obj[key] = value. Это используется главным образом для подклассов словарей, но может применяться и для других классов, если они реализуют__setitem__(). -
Необязательный вызываемый объект с сигнатурой
(obj, state). Этот вызываемый объект позволяет программно управлять обновлением состояния конкретного объекта вместо использования статического метода__setstate__()объектаobj. Если он не равенNone, этот вызываемый объект имеет приоритет над методом__setstate__()объектаobj.Добавлено в версии 3.8: Добавлен необязательный шестой элемент кортежа,
(obj, state).
-
object.__reduce_ex__(protocol) -
В качестве альтернативы можно определить метод
__reduce_ex__(). Единственное отличие состоит в том, что этот метод должен принимать один целочисленный аргумент — версию протокола. Если он определён, pickle отдаёт ему предпочтение перед методом__reduce__(). Кроме того,__reduce__()автоматически становится синонимом расширенной версии. Основное назначение этого метода — предоставлять обратно совместимые значения свёртки для более старых выпусков Python.
Сохранение внешних объектов
Для обеспечения сохранности объектов модуль pickle поддерживает концепцию ссылки на объект за пределами потока сериализованных данных. На такие объекты ссылаются с помощью постоянного идентификатора, который должен быть либо строкой из буквенно-цифровых символов (для протокола 0) [5], либо произвольным объектом (для любого более нового протокола).
Разрешение таких постоянных идентификаторов не определяется модулем pickle; для этого он делегирует разрешение пользовательским методам упаковщика и распаковщика — соответственно persistent_id() и persistent_load().
Чтобы сериализовать объекты с внешним постоянным идентификатором, упаковщик должен иметь пользовательский метод persistent_id(), который принимает объект в качестве аргумента и возвращает либо None, либо постоянный идентификатор этого объекта. Если возвращается None, упаковщик просто сериализует объект обычным способом. Если возвращается строка с постоянным идентификатором, упаковщик сериализует этот объект вместе с маркером, позволяющим распаковщику распознать его как постоянный идентификатор.
Для десериализации внешних объектов распаковщик должен иметь пользовательский метод persistent_load(), который принимает объект — постоянный идентификатор и возвращает объект, на который тот ссылается.
Ниже приведён подробный пример использования постоянных идентификаторов для сериализации внешних объектов по ссылке.
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
Таблицы диспетчеризации
Если требуется настроить сериализацию некоторых классов, не затрагивая другой код, зависящий от сериализации, можно создать упаковщик с собственной таблицей диспетчеризации.
Глобальная таблица диспетчеризации, управляемая модулем copyreg, доступна как copyreg.dispatch_table. Поэтому можно использовать изменённую копию copyreg.dispatch_table в качестве собственной таблицы диспетчеризации.
Например,
f = io.BytesIO() p = pickle.Pickler(f) p.dispatch_table = copyreg.dispatch_table.copy() p.dispatch_table[SomeClass] = reduce_SomeClass
создаёт экземпляр pickle.Pickler с собственной таблицей диспетчеризации, которая обрабатывает класс SomeClass особым образом. В качестве альтернативы код
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
делает то же самое, но все экземпляры MyPickler по умолчанию будут использовать общую собственную таблицу диспетчеризации. С другой стороны, код
copyreg.pickle(SomeClass, reduce_SomeClass) f = io.BytesIO() p = pickle.Pickler(f)
изменяет глобальную таблицу диспетчеризации, общую для всех пользователей модуля copyreg.
Обработка объектов с состоянием
Вот пример, демонстрирующий, как изменить поведение сериализации класса. Класс TextReader ниже открывает текстовый файл и при каждом вызове метода readline() возвращает номер строки и её содержимое. Если сериализуется экземпляр TextReader, сохраняются все атрибуты, кроме атрибута — объекта файла. При десериализации экземпляра файл открывается заново, а чтение продолжается с последней позиции. Для реализации этого поведения используются методы __setstate__() и __getstate__().
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
self.file = file
Пример использования может выглядеть так:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
Пользовательская свёртка типов, функций и других объектов
Добавлено в версии 3.8.
В некоторых случаях dispatch_table может оказаться недостаточно гибким. В частности, может потребоваться настраивать сериализацию по критерию, отличному от типа объекта, или настраивать сериализацию функций и классов.
В таких случаях можно создать подкласс класса Pickler и реализовать метод reducer_override(). Этот метод может возвращать произвольный кортеж свёртки (см. __reduce__()). Также он может возвращать NotImplemented, чтобы перейти к традиционному поведению.
Если определены и dispatch_table, и reducer_override(), приоритет имеет метод reducer_override().
Примечание
Из соображений производительности reducer_override() может не вызываться для следующих объектов: None, True, False и точных экземпляров int, float, bytes, str, dict, set, frozenset, list и tuple.
Ниже приведён простой пример, в котором мы разрешаем сериализацию и восстановление заданного класса:
import io
import pickle
class MyClass:
my_attribute = 1
class MyPickler(pickle.Pickler):
def reducer_override(self, obj):
"""Custom reducer for MyClass."""
if getattr(obj, "__name__", None) == "MyClass":
return type, (obj.__name__, obj.__bases__,
{'my_attribute': obj.my_attribute})
else:
# For any other object, fallback to usual reduction
return NotImplemented
f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)
del MyClass
unpickled_class = pickle.loads(f.getvalue())
assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1
Внеполосные буферы
Добавлено в версии 3.8.
В некоторых случаях модуль pickle используется для передачи больших объёмов данных. Поэтому для сохранения производительности и сокращения потребления ресурсов может быть важно минимизировать число копирований памяти. Однако обычная работа модуля pickle, преобразующего графоподобную структуру объектов в последовательный поток байтов, неизбежно подразумевает копирование данных в поток pickle и из него.
Это ограничение можно обойти, если и поставщик (реализация типов объектов, предназначенных для передачи), и получатель (реализация системы связи) поддерживают средства внеполосной передачи, предоставляемые протоколом pickle 5 и новее.
API поставщика
Сериализуемые объекты с большими объёмами данных должны реализовывать метод __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)
См. также
-
Modulecopyreg -
Регистрация конструкторов интерфейса pickle для типов расширения.
-
Modulepickletools -
Инструменты для работы с сериализованными данными и их анализа.
-
Moduleshelve -
Индексированные базы данных объектов; используется
pickle. -
Modulecopy -
Поверхностное и глубокое копирование объектов.
-
Modulemarshal -
Высокопроизводительная сериализация встроенных типов.
Сноски
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/pickle.html