Spec-Zone.ru › Python 3.5

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

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

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

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

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

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

1.1. Сравнение с marshal

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

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

  • Модуль pickle отслеживает объекты, которые он уже сериализовал, поэтому последующие ссылки на тот же объект не будут сериализованы повторно. marshal этого не делает.

    Это имеет последствия как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты — это объекты, которые содержат ссылки на себя. Они не обрабатываются модулем marshal, и, фактически, попытка маршаллить рекурсивные объекты приведёт к сбою интерпретатора Python. Обмен объектами происходит, когда существует несколько ссылок на один и тот же объект в разных местах иерархии сериализуемых объектов. pickle сохраняет такие объекты только один раз и гарантирует, что все остальные ссылки указывают на главную копию. Обмен объектами сохраняется, что может быть очень важно для изменяемых объектов.

  • marshal нельзя использовать для сериализации пользовательских классов и их экземпляров. pickle может сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при хранении объекта.
  • Формат сериализации marshal не гарантирует переносимость между версиями Python. Поскольку его основная задача — поддержка .pyc файлов, разработчики Python оставляют за собой право изменять формат сериализации несовместимым образом, если это необходимо. Формат сериализации pickle гарантирует обратную совместимость между версиями Python.

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.

2. Формат потока данных

Формат данных, используемый модулем pickle, специфичен для Python. Это даёт преимущество в отсутствии ограничений, налагаемых внешними стандартами, такими как JSON или XDR (которые не могут представлять обмен указателями); однако это означает, что программы, не написанные на Python, могут не иметь возможности восстановить сериализованные Python-объекты.

По умолчанию формат данных pickle использует относительно компактное двоичное представление. Если вам требуются оптимальные характеристики по размеру, вы можете эффективно сжать данные pickle.

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

В настоящее время существует 5 различных протоколов, которые можно использовать для заморозки. Чем выше используемый протокол, тем более новой версии Python требуется для чтения сгенерированного pickle.

  • Протокол версии 0 — оригинальный «удобочитаемый» протокол и обратной совместимостью с более ранними версиями Python.
  • Протокол версии 1 — старый двоичный формат, который также совместим с более ранними версиями Python.
  • Протокол версии 2 был введён в Python 2.3. Он обеспечивает гораздо более эффективную заморозку классов нового стиля. Сведения об улучшениях, привнесённых протоколом 2, можно найти в PEP 307.
  • Протокол версии 3 был добавлен в Python 3.0. Он имеет явную поддержку bytes объектов и не может быть разморожен Python 2.x. Это протокол по умолчанию и рекомендуемый протокол, когда требуется совместимость с другими версиями Python 3.
  • Протокол версии 4 был добавлен в Python 3.4. Он добавляет поддержку очень больших объектов, заморозку большего количества типов объектов и некоторые оптимизации формата данных. Сведения об улучшениях, привнесённых протоколом 4, можно найти в PEP 3154.

Примечание

Сериализация — это более примитивное понятие, чем сохранение; хотя pickle читает и записывает объекты файлов, он не обрабатывает проблему именования сохраняемых объектов, а также (ещё более сложную) проблему одновременного доступа к сохраняемым объектам. Модуль pickle может преобразовать сложный объект в поток байтов и преобразовать поток байтов в объект с той же внутренней структурой. Возможно, наиболее очевидным применением этих потоков байтов является их запись в файл, но также их можно отправить по сети или сохранить в базе данных. Модуль shelve предоставляет простой интерфейс для заморозки и разморозки объектов в файлах базы данных в стиле DBM.

3. Интерфейс модуля

Для сериализации иерархии объектов достаточно вызвать функцию dumps(). Аналогично, для десериализации потока данных вызывается функция loads(). Однако, если вам нужен больший контроль над сериализацией и десериализацией, вы можете создать объект Pickler или Unpickler, соответственно.

Модуль pickle предоставляет следующие константы:

pickle.HIGHEST_PROTOCOL

Целое число, наивысшая версия протокола протокола, доступная. Это значение может быть передано в качестве значения 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(), принимающий один аргумент типа bytes. Таким образом, он может быть файлом на диске, открытым для двоичного записи, экземпляром 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 определяется автоматически, поэтому аргумент протокола не требуется. Байты, расположенные после представления сериализованного объекта, игнорируются.

Аргумент 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.

pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")

Читает иерархию сериализованных объектов из объекта bytes и возвращает реконструированную иерархию объектов, указанную в нём.

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

Необязательные ключевые аргументы — 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.

Модуль 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(), если вам нужны более компактные сериализации.

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-битных как байтовые объекты.

load()

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

persistent_load(pid)

По умолчанию вызывает UnpicklingError.

Если определён, persistent_load() должен возвращать объект, указанный постоянным идентификатором pid. Если обнаружен неверный постоянный идентификатор, следует вызвать UnpicklingError.

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

find_class(module, name)

Если необходимо, импортировать module и вернуть объект, названный name, из него, где аргументы module и name являются объектами str. Обратите внимание, что, вопреки своему названию, find_class() также используется для поиска функций.

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

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__().

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__()

В протоколах 4 и новее, классы, реализующие метод __getnewargs_ex__(), могут диктовать значения, передаваемые методу __new__() при десериализации. Метод должен вернуть пару (args, kwargs) , где args — кортеж позиционных аргументов, а kwargs — словарь именованных аргументов для создания объекта. Они будут переданы методу __new__() при десериализации.

Вы должны реализовать этот метод, если методу __new__() вашего класса требуются только именованные аргументы. В противном случае, для совместимости рекомендуется реализовать __getnewargs__().

object.__getnewargs__()

Этот метод выполняет аналогичную задачу, что и __getnewargs_ex__(), но для протоколов 2 и новее. Он должен вернуть кортеж аргументов args , которые будут переданы методу __new__() при десериализации.

В протоколах 4 и новее, __getnewargs__() не будет вызван, если определен __getnewargs_ex__().

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__() автоматически становится синонимом расширенной версии. Основное применение этого метода — предоставление обратной совместимости значений сокращения для более старых версий Python.

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()

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)

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!'

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 или сторонние решения.

7. Производительность

Недавние версии протокола сериализации pickle (от протокола 2 и выше) имеют эффективные двоичные кодировки для нескольких общих функций и встроенных типов. Кроме того, модуль pickle имеет прозрачный оптимизатор, написанный на C.

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)

См. также

Module copyreg
Регистрация конструктора интерфейса сериализации для типов расширений.
Module pickletools
Инструменты для работы с данными сериализации и анализа данных сериализации.
Module shelve
Индексированные базы данных объектов; использует pickle.
Module copy
Копирование объектов поверхностно и глубоко.
Module marshal
Высокопроизводительная сериализация встроенных типов.

Примечания

[1] Не путайте с модулем marshal
[2] Вот почему функции lambda не могут быть сериализованы: все функции lambda имеют одно и то же имя: <lambda>.
[3] Возникающее исключение, скорее всего, будет ImportError или AttributeError, но это может быть и что-то другое.
[4] Модуль copy использует этот протокол для поверхностного и глубокого копирования.
[5] Ограничение на символы из алфавитно-цифрового набора обусловлено тем, что постоянные идентификаторы в протоколе 0 разделяются символом новой строки. Поэтому, если в постоянных идентификаторах встречаются символы новой строки, результирующая сериализация станет нечитаемой.

© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.5/library/pickle.html

Spec-Zone.ru

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