pickle — Сериализация объектов Python
Исходный код: Lib/pickle.py
Модуль pickle реализует двоичные протоколы для сериализации и десериализации структуры данных Python. «Заморозка» — это процесс преобразования иерархии объектов Python в последовательность байтов, а «отморозка» — обратное преобразование последовательности байтов (из двоичного файла или объекта-последовательности байтов) обратно в иерархию объектов. Заморозку (и отморозку) также называют «сериализацией», «маршалингом», 1 или «сглаживанием»; однако, чтобы избежать путаницы, в данном документе используются термины «заморозка» и «отморозка».
Предупреждение
Модуль pickle не является безопасным в отношении ошибочных или злонамеренно сконструированных данных. Никогда не распаковывайте данные, полученные из ненадежного или неаутентифицированного источника.
12.1.1. Взаимосвязь с другими модулями Python
12.1.1.1. Сравнение с marshal
В Python есть более примитивный модуль сериализации, называемый marshal, но в целом pickle всегда должен быть предпочтительным способом сериализации объектов Python. marshal существует в основном для поддержки файлов Python .pyc.
Модуль pickle отличается от marshal несколькими существенными способами:
-
Модуль
pickleотслеживает объекты, которые он уже сериализовал, так что последующие ссылки на тот же объект не будут сериализованы повторно.marshalэтого не делает.Это имеет последствия как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты — это объекты, содержащие ссылки на самих себя. Они не обрабатываются marshal, и на самом деле попытка маршаллирования рекурсивных объектов приведет к сбою интерпретатора Python. Совместное использование объектов происходит, когда на тот же объект в различных местах иерархии сериализуемых объектов имеются несколько ссылок.
pickleсохраняет такие объекты только один раз и гарантирует, что все другие ссылки указывают на главный экземпляр. Объекты, используемые совместно, остаются совместно используемыми, что очень важно для изменяемых объектов. -
marshalне может использоваться для сериализации пользовательских классов и их экземпляров.pickleможет сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемо и находиться в том же модуле, что и при сохранении объекта. - Формат сериализации
marshalне гарантирует переносимость между версиями Python. Поскольку его основная задача — поддержка файлов.pyc, разработчики Python оставляют за собой право изменять формат сериализации несовместимым обратным образом, если это необходимо. Формат сериализацииpickleгарантирует обратную совместимость между выпусками Python.
12.1.1.2. Сравнение с json
Существуют фундаментальные различия между протоколами pickle и JSON (JavaScript Object Notation):
- JSON — это текстовый формат сериализации (он выводит текст unicode, хотя в большинстве случаев он затем кодируется в
utf-8), в то время как pickle — двоичный формат сериализации; - JSON — удобочитаемый, а pickle — нет;
- JSON — взаимозаменяемый и широко используется вне экосистемы Python, в то время как pickle — специфичен для Python;
- JSON по умолчанию может представлять только подмножество встроенных типов Python, и не может представлять пользовательские классы; pickle может представлять огромное количество типов Python (многие из них автоматически, с помощью умного использования средств интроспекции Python; сложные случаи могут быть рассмотрены путем реализации специфических API для объектов).
См. также
Модуль json: модуль стандартной библиотеки, позволяющий сериализацию и десериализацию JSON.
12.1.2. Формат потока данных
Формат данных, используемый модулем pickle, специфичен для Python. Это имеет преимущество, заключающееся в отсутствии ограничений, накладываемых внешними стандартами, такими как JSON или XDR (которые не могут представлять совместное использование указателей); однако это означает, что программы, не написанные на Python, могут не быть в состоянии восстановить сериализованные объекты Python.
По умолчанию формат данных pickle использует относительно компактное двоичное представление. Если вам необходимы оптимальные характеристики размера, вы можете эффективно сжать сериализованные данные.
Модуль pickletools содержит инструменты для анализа потоков данных, генерируемых модулем pickle. Исходный код pickletools содержит обширные комментарии об операциях, используемых протоколами pickle.
В настоящее время существует 5 различных протоколов, которые можно использовать для заморозки. Чем выше используемый протокол, тем более поздней версии Python требуется для чтения созданного pickle.
- Протокол версии 0 — это исходный «человекочитаемый» протокол и обратная совместимость с более ранними версиями Python.
- Протокол версии 1 — это старый двоичный формат, который также совместим с более ранними версиями Python.
- Протокол версии 2 был введен в Python 2.3. Он обеспечивает гораздо более эффективную заморозку классов нового стиля. Обратитесь к PEP 307 за информацией об улучшениях, внесенных протоколом 2.
- Протокол версии 3 был добавлен в Python 3.0. Он имеет явную поддержку объектов
bytesи не может быть распакован Python 2.x. Это протокол по умолчанию и рекомендуемый протокол, когда требуется совместимость с другими версиями Python 3. - Протокол версии 4 был добавлен в Python 3.4. Он добавляет поддержку очень больших объектов, заморозку большего количества видов объектов и некоторые оптимизации формата данных. Обратитесь к PEP 3154 за информацией об улучшениях, внесенных протоколом 4.
Примечание
Сериализация — это более примитивное понятие, чем сохранение; хотя pickle читает и записывает объекты файлов, он не обрабатывает проблему именования сохраняемых объектов, а также (еще более сложную) проблему одновременного доступа к сохраняемым объектам. Модуль pickle может преобразовать сложный объект в последовательность байтов, и он может преобразовать последовательность байтов обратно в объект с той же внутренней структурой. Возможно, наиболее очевидным действием с этими последовательностями байтов является запись их в файл, но также можно отправить их по сети или сохранить в базе данных. Модуль shelve предоставляет простой интерфейс для заморозки и отморозки объектов в файлах базы данных в стиле DBM.
12.1.3. Интерфейс модуля
Для сериализации иерархии объектов просто вызовите функцию dumps(). Аналогично, для десериализации потока данных вызовите функцию loads(). Однако, если вам нужен больший контроль над сериализацией и десериализацией, вы можете создать объект Pickler или Unpickler соответственно.
Модуль pickle предоставляет следующие константы:
-
pickle.HIGHEST_PROTOCOL -
Целое число, наивысшая версия протокола протокола, доступная. Это значение может быть передано как значение протокола функциям
dump()иdumps(), а также конструкторуPickler.
-
pickle.DEFAULT_PROTOCOL -
Целое число, значение по умолчанию для версии протокола, используемого для сериализации. Может быть меньше, чем
HIGHEST_PROTOCOL. В настоящее время протокол по умолчанию — 3, новый протокол, разработанный для Python 3.
Модуль pickle предоставляет следующие функции для удобства процесса сериализации:
-
pickle.dump(obj, file, protocol=None, *, fix_imports=True) -
Записать сериализованное представление объекта obj в открытый объект файла file. Это эквивалентно
Pickler(file, protocol).dump(obj).Необязательный аргумент protocol, целое число, указывает сериализатору использовать указанный протокол; поддерживаемые протоколы — от 0 до
HIGHEST_PROTOCOL. Если не указано, значение по умолчанию —DEFAULT_PROTOCOL. Если указано отрицательное число, используетсяHIGHEST_PROTOCOL.Аргумент file должен иметь метод write(), принимающий один байтовый аргумент. Таким образом, это может быть файл на диске, открытый для двоичного записи, объект
io.BytesIOили любой другой пользовательский объект, удовлетворяющий этому интерфейсу.Если fix_imports имеет значение True и protocol меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных pickle был читаем Python 2.
-
pickle.dumps(obj, protocol=None, *, fix_imports=True) -
Возвращает сериализованное представление объекта как объект
bytes, вместо записи его в файл.Аргументы protocol и fix_imports имеют то же значение, что и в
dump().
-
pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict") -
Прочитать сериализованное представление объекта из открытого объекта файла file и вернуть реконструированную иерархию объектов, указанную в нём. Это эквивалентно
Unpickler(file).load().Версия протокола pickle определяется автоматически, поэтому аргумент protocol не нужен. Байты, следующие за представлением сериализованного объекта, игнорируются.
Аргумент file должен иметь два метода: метод read(), принимающий целочисленный аргумент, и метод readline(), не требующий аргументов. Оба метода должны возвращать байты. Таким образом, 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'.
-
pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict") -
Прочитать иерархию сериализованных объектов из объекта
bytesи вернуть реконструированную иерархию объектов, указанную в нём.Версия протокола pickle определяется автоматически, поэтому аргумент protocol не нужен. Байты, следующие за представлением сериализованного объекта, игнорируются.
Необязательные ключевые аргументы — 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'.
Модуль pickle определяет три исключения:
-
exception pickle.PickleError -
Базовый класс для других исключений сериализации. Он наследует
Exception.
-
exception pickle.PicklingError -
Исключение, которое возникает, когда сериализатор
Picklerвстречает несериализуемый объект. Оно наследуетPickleError.Обратитесь к Какие объекты могут быть сериализованы и десериализованы?, чтобы узнать, какие типы объектов могут быть сериализованы.
-
exception pickle.UnpicklingError -
Исключение, которое возникает при проблемах с десериализацией объекта, таких как повреждение данных или нарушение безопасности. Оно наследует
PickleError.Обратите внимание, что во время десериализации могут возникнуть и другие исключения, включая (но не ограничиваясь ими) AttributeError, EOFError, ImportError и IndexError.
Модуль pickle экспортирует два класса: Pickler и Unpickler.
-
class pickle.Pickler(file, protocol=None, *, fix_imports=True) -
Это принимает бинарный файл для записи потока данных pickle.
Необязательный аргумент protocol, целое число, сообщает pickler использовать указанный протокол; поддерживаемые протоколы — от 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.
-
dump(obj) -
Записать закодированное представление obj в открытый файл, указанный в конструкторе.
-
persistent_id(obj) -
По умолчанию ничего не делает. Это нужно, чтобы подкласс мог переопределить его.
Если
persistent_id()возвращаетNone, obj сериализуется обычным образом. Любое другое значение заставляетPicklerвыдать возвращённое значение в качестве постоянного идентификатора для obj. Значение этого постоянного идентификатора должно быть определеноUnpickler.persistent_load(). Обратите внимание, что возвращаемое значениеpersistent_id()само не может иметь постоянный идентификатор.Подробности и примеры использования см. в разделе Сохранение внешних объектов.
-
dispatch_table -
Таблица диспетчеризации объекта pickler — это регистр функций сокращения, которые могут быть объявлены с использованием
copyreg.pickle(). Это отображение, ключами которого являются классы, а значениями — функции сокращения. Функция сокращения принимает единственный аргумент соответствующего класса и должна соответствовать тому же интерфейсу, что и метод__reduce__().По умолчанию у объекта pickler не будет атрибута
dispatch_table, и он будет использовать глобальную таблицу диспетчеризации, управляемую модулемcopyreg. Однако для настройки сериализации для определённого объекта pickler можно установить атрибутdispatch_tableна объект типа dict. В качестве альтернативы, если подклассPicklerимеет атрибутdispatch_table, то он будет использоваться в качестве таблицы диспетчеризации по умолчанию для экземпляров этого класса.Примеры использования см. в разделе Таблицы диспетчеризации.
Новое в версии 3.3.
-
fast -
Устарело. Включить быстрый режим, если значение True. Быстрый режим отключает использование мемо, тем самым ускоряя процесс сериализации, не генерируя излишние инструкции PUT. Не следует использовать с объектами, ссылающимися на себя, так как в противном случае
Picklerбудет рекурсировать бесконечно.Используйте
pickletools.optimize(), если вам нужно более компактное представление pickle.
-
-
class pickle.Unpickler(file, *, fix_imports=True, encoding="ASCII", errors="strict") -
Это принимает бинарный файл для чтения потока данных pickle.
Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется.
Аргумент file должен иметь два метода: метод read(), принимающий целочисленный аргумент, и метод readline(), не требующий аргументов. Оба метода должны возвращать значения типа bytes. Таким образом, 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.
-
load() -
Прочитать закодированное представление объекта из открытого файла, указанного в конструкторе, и вернуть реконструированную иерархию объектов, указанную в нём. Байты, расположенные после представления закодированного объекта, игнорируются.
-
persistent_load(pid) -
По умолчанию поднимает
UnpicklingError.Если определено,
persistent_load()должно возвратить объект, указанный постоянным идентификатором pid. Если встречен недействительный постоянный идентификатор, должно быть поднято исключениеUnpicklingError.Подробности и примеры использования см. в разделе Сохранение внешних объектов.
-
find_class(module, name) -
Импортировать module при необходимости и вернуть объект, называемый name из него, где аргументы module и name — объекты
str. Обратите внимание, что, вопреки своему названию,find_class()также используется для поиска функций.Подклассы могут переопределить его, чтобы получить контроль над типом объектов и способами их загрузки, потенциально снижая риски безопасности. Подробности см. в разделе Ограничение глобальных переменных.
-
12.1.4. Что можно сериализовать и десериализовать?
Следующие типы можно сериализовать:
-
None,True, иFalse - целые, числа с плавающей запятой, комплексные числа
- строки, байты, массивы байтов
- кортежи, списки, множества и словари, содержащие только сериализуемые объекты
- функции, определённые на верхнем уровне модуля (используя
def, а неlambda) - встроенные функции, определённые на верхнем уровне модуля
- классы, определённые на верхнем уровне модуля
- экземпляры таких классов, чьё
__dict__или результат вызова__getstate__()сериализуемо (подробнее см. раздел Сериализация экземпляров классов).
Попытка сериализовать несериализуемые объекты вызовет исключение PicklingError; в этом случае может быть уже записано неопределённое количество байтов в подлежащий файл. Попытка сериализовать сильно рекурсивную структуру данных может превысить максимальную глубину рекурсии, в этом случае будет поднято исключение RecursionError. Вы можете осторожно увеличить этот предел с помощью sys.setrecursionlimit().
Обратите внимание, что функции (встроенные и пользовательские) сериализуются по «полному квалифицированному» имени ссылки, а не по значению. 2 Это означает, что сериализуется только имя функции, а также имя модуля, в котором определена функция. Ни код функции, ни какие-либо её атрибуты не сериализуются. Следовательно, определяющий модуль должен быть импортируем в среде десериализации, а модуль должен содержать именованный объект; в противном случае будет поднято исключение. 3
Точно так же классы сериализуются по имени ссылки, поэтому в среде десериализации применяются те же ограничения. Обратите внимание, что ни код, ни данные класса не сериализуются, поэтому в следующем примере атрибут класса attr не восстанавливается в среде десериализации:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
Эти ограничения объясняют, почему сериализуемые функции и классы должны быть определены на верхнем уровне модуля.
Аналогично, при сериализации экземпляров классов, код и данные их класса не сериализуются вместе с ними. Сериализуются только данные экземпляра. Это сделано намеренно, чтобы вы могли исправить ошибки в классе или добавить методы в класс и при этом загружать объекты, созданные с более ранней версией класса. Если вы планируете использовать долгоживущие объекты, которые будут взаимодействовать со многими версиями класса, может быть полезно поместить номер версии в объекты, чтобы класс мог выполнить соответствующие преобразования с помощью метода __setstate__().
12.1.5. Сериализация экземпляров классов
В этом разделе мы опишем общие механизмы, которые доступны для определения, настройки и управления тем, как экземпляры классов сериализуются и десериализуются.
В большинстве случаев для сериализации экземпляров дополнительных кодов не требуется. По умолчанию модуль pickle будет извлекать класс и атрибуты экземпляра с помощью интроспекции. При десериализации экземпляра класса его метод __init__() обычно не вызывается. Вначале выполняется создание неинициализированного экземпляра, а затем восстанавливаются сохраненные атрибуты. Следующий код демонстрирует реализацию этого поведения:
def save(obj):
return (obj.__class__, obj.__dict__)
def load(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, метод
__getnewargs__()вызывался вместо__getnewargs_ex__()в протоколах 2 и 3.
-
object.__getstate__() -
Классы могут дополнительно влиять на способ сериализации своих экземпляров; если класс определяет метод
__getstate__(), он вызывается, и возвращаемый объект сериализуется как содержимое экземпляра, вместо содержимого словаря экземпляра. Если метод__getstate__()отсутствует, словарь экземпляра__dict__сериализуется как обычно.
-
object.__setstate__(state) -
При десериализации, если класс определяет
__setstate__(), он вызывается со состояние, десериализованным из данных. В этом случае нет необходимости, чтобы объект состояния был словарем. В противном случае сериализованное состояние должно быть словарем, а его элементы присваиваются новому экземпляру.Примечание
Если
__getstate__()возвращает ложное значение, метод__setstate__()не будет вызван при десериализации.
Дополнительную информацию о методах __getstate__() и __setstate__() см. в разделе Обработка состояний объектов.
Примечание
Во время десериализации некоторые методы, такие как __getattr__(), __getattribute__() или __setattr__(), могут быть вызваны для экземпляра. В случае если эти методы зависят от того, что какое-то внутреннее инвариантное свойство является истинным, тип должен реализовывать __getnewargs__() или __getnewargs_ex__() для установления такого инварианта; в противном случае ни __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__().
-
object.__reduce_ex__(protocol) -
В качестве альтернативы, можно определить метод
__reduce_ex__(). Единственное различие заключается в том, что этот метод должен принимать один целочисленный аргумент — версию протокола. При определении pickle будет отдавать предпочтение ему перед методом__reduce__(). Кроме того, метод__reduce__()автоматически становится синонимом расширенной версии. Основное применение этого метода заключается в предоставлении обратной совместимости значений reduce для более старых версий Python.
12.1.5.1. Сохранение внешних объектов
Для обеспечения сохранения объектов модуль 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()
12.1.5.2. Таблицы перенаправления
Если необходимо настроить сериализацию некоторых классов, не затрагивая другой код, зависящий от сериализации, можно создать сериализатор с частной таблицей перенаправления.
Глобальная таблица перенаправления, управляемая модулем 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, выглядит следующим образом
copyreg.pickle(SomeClass, reduce_SomeClass) f = io.BytesIO() p = pickle.Pickler(f)
12.1.5.3. Обработка состояний объектов
Вот пример, показывающий, как изменить поведение сериализации для класса. Класс 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!'
12.1.6. Ограничение глобальных переменных
По умолчанию десериализация импортирует любой класс или функцию, который она находит в данных сериализации. Для многих приложений это поведение неприемлемо, поскольку оно позволяет десериализатору импортировать и вызывать произвольный код. Просто представьте, что делает этот сгенерированный поток данных сериализации при загрузке:
>>> 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 или сторонние решения.
12.1.7. Производительность
Недавние версии протокола pickle (от протокола 2 и выше) имеют эффективные двоичные кодировки для нескольких общих функций и встроенных типов. Кроме того, модуль pickle имеет прозрачный оптимизатор, написанный на C.
12.1.8. Примеры
Для самого простого кода используйте функции dump() и load().
import pickle
# An arbitrary collection of objects supported by pickle.
data = {
'a': [1, 2.0, 3, 4+6j],
'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 -
Высокопроизводительная сериализация встроенных типов.
Примечания
-
1 -
Не путайте с модулем
marshal -
2 -
Вот почему анонимные функции
lambdaнельзя сериализовать: все анонимные функцииlambdaиспользуют одно имя:<lambda>. -
3 -
Возникающее исключение, скорее всего, будет
ImportErrorилиAttributeError, но это может быть и что-то другое. -
4 -
Модуль
copyиспользует этот протокол для операций поверхностного и глубокого копирования. -
5 -
Ограничение на символы алфавитно-цифрового набора обусловлено тем, что идентификаторы постоянства в протоколе 0 разделяются символом новой строки. Таким образом, если в идентификаторах постоянства встречаются символы новой строки, полученный файл pickle станет нечитаемым.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.6/library/pickle.html