Spec-Zone.ru › Python 3.8

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

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

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

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

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

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

Рассмотрите возможность подписи данных с помощью hmac, если вам необходимо убедиться, что они не были изменены.

Более безопасные форматы сериализации, такие как json, могут быть более подходящими, если вы обрабатываете недоверенные данные. См. Сравнение с json.

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

Сравнение с marshal

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

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

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

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

  • marshal нельзя использовать для сериализации пользовательских классов и их экземпляров. pickle может сохранять и восстанавливать экземпляры классов прозрачно, однако определение класса должно быть импортируемым и находиться в том же модуле, что и при хранении объекта.
  • Формат сериализации marshal не гарантирует переносимость между версиями Python. Поскольку его основная задача заключается в поддержке файлов .pyc, разработчики Python оставляют за собой право изменять формат сериализации несовместимым образом, если это потребуется. Формат сериализации pickle гарантирует обратную совместимость между версиями Python при условии выбора совместимого протокола pickle и кода pickling и unpickling обрабатывает различия между типами 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. Он обеспечивает гораздо более эффективное кодирование классов нового стиля. Обратитесь к PEP 307 за информацией об улучшениях, внесенных протоколом 2.
  • Протокол версии 3 был добавлен в Python 3.0. Он имеет явную поддержку объектов bytes и не может быть распакован Python 2.x. Это был протокол по умолчанию в Python 3.0–3.7.
  • Протокол версии 4 был добавлен в Python 3.4. Он добавляет поддержку очень больших объектов, кодирование большего количества типов объектов и некоторые оптимизации формата данных. Он является протоколом по умолчанию, начиная с Python 3.8. Обратитесь к PEP 3154 за информацией об улучшениях, внесенных протоколом 4.
  • Протокол версии 5 был добавлен в Python 3.8. Он добавляет поддержку данных вне канала и ускорение для данных внутри канала. Обратитесь к PEP 574 за информацией об улучшениях, внесенных протоколом 5.

Примечание

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

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

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

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

pickle.HIGHEST_PROTOCOL

Целое число, наивысшая доступная версия протокола. Это значение может быть передано в качестве значения protocol функциям dump() и dumps(), а также конструктору Pickler.

pickle.DEFAULT_PROTOCOL

Целое число, версия протокола по умолчанию, используемая для сериализации. Может быть меньше, чем HIGHEST_PROTOCOL. В настоящее время протокол по умолчанию равен 4, впервые представлен в Python 3.4 и несовместим с предыдущими версиями.

Изменено в версии 3.0: Протокол по умолчанию равен 3.

Изменено в версии 3.8: Протокол по умолчанию равен 4.

Модуль 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 должен быть объектом подобным байтам.

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

Аргументы file, fix_imports, encoding, errors, strict и buffers имеют то же значение, что и в конструкторе Unpickler.

Изменено в версии 3.8: Добавлен аргумент buffers.

Модуль pickle определяет три исключения:

exception pickle.PickleError

Общий базовый класс для других исключений сериализации. Он наследует Exception.

exception pickle.PicklingError

Исключение, генерируемое, когда Pickler сталкивается с несериализуемым объектом. Оно наследует PickleError.

Обратитесь к Что может быть сериализовано и десериализовано?, чтобы узнать какие типы объектов могут быть сериализованы.

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. Однако, чтобы настроить сериализацию для конкретного объекта пиклера, можно установить атрибут dispatch_table на объект типа dict. В качестве альтернативы, если подкласс Pickler имеет атрибут dispatch_table, то он будет использоваться в качестве таблицы распределения по умолчанию для экземпляров этого класса.

См. Таблицы распределения для примеров использования.

Добавлена в версии 3.3.

reducer_override(self, obj)

Специальный редуктор, который можно определить в подклассах Pickler. Этот метод имеет приоритет над любым редуктором в dispatch_table. Он должен соответствовать тому же интерфейсу, что и метод __reduce__(), и может необязательно вернуть NotImplemented для возврата к редукторам, зарегистрированным в dispatch_table, для сериализации obj.

Подробный пример см. в разделе Настройка сокращения для типов, функций и других объектов.

Добавлена в версии 3.8.

fast

Устарело. Включить быстрый режим, если значение равно true. Быстрый режим отключает использование запоминающего устройства, поэтому ускоряет процесс сериализации, не генерируя излишние операции PUT. Не следует использовать с объектами, ссылающимися на себя; в противном случае Pickler будет рекурсивно вызываться бесконечно.

Используйте pickletools.optimize(), если вам нужны более компактные pickle.

class pickle.Unpickler(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

Это принимает двоичный файл для чтения потока данных pickle.

Версия протокола pickle определяется автоматически, поэтому аргумент протокола не требуется.

Аргумент file должен иметь три метода: метод read() с целочисленным аргументом, метод readinto() с аргументом буфера и метод readline() без аргументов, как в интерфейсе io.BufferedIOBase. Таким образом, file может быть файлом на диске, открытым для двоичного чтения, объектом io.BytesIO или любым другим пользовательским объектом, который соответствует этому интерфейсу.

Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости для потока pickle, сгенерированного Python 2. Если fix_imports имеет значение true, pickle попытается сопоставить старые имена Python 2 с новыми именами, используемыми в Python 3. encoding и errors сообщают pickle, как декодировать 8-битные строковые экземпляры, закодированные Python 2; по умолчанию они равны ‘ASCII’ и ‘strict’ соответственно. encoding может быть ‘bytes’, чтобы читать эти 8-битные строковые экземпляры как объекты типа bytes. Использование encoding='latin1' необходимо для распаковки массивов NumPy и экземпляров datetime, date и time, закодированных Python 2.

Если 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)

Обёртка для буфера, представляющего данные, подлежащие сериализации. buffer должен быть объектом, предоставляющим буфер, например, объектом типа bytes-like object или N-мерным массивом.

PickleBuffer сам по себе предоставляет буфер, поэтому его можно передать в другие API, ожидающие объект, предоставляющий буфер, например, в memoryview.

Объекты PickleBuffer могут быть сериализованы только с помощью протокола pickle 5 или выше. Они подходят для внеполосной сериализации.

Добавлена в версии 3.8.

raw()

Возвращает memoryview области памяти, лежащей в основе этого буфера. Возвращаемый объект — это одномерный, C-непрерывный memoryview с форматом B (беззнаковые байты). Если буфер не является ни C-, ни Fortran-непрерывным, возникает BufferError.

release()

Освободить буфер, представленный объектом PickleBuffer.

Что можно сериализовать и десериализовать?

Следующие типы можно сериализовать:

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

Десериализация экземпляров классов

В этом разделе описываются общие механизмы, доступные для определения, настройки и управления тем, как экземпляры классов сериализуются и десериализуются.

В большинстве случаев для того, чтобы экземпляры были сериализуемыми, дополнительных кодов не требуется. По умолчанию, 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__(), могут быть вызваны для экземпляра. В случае, если эти методы полагаются на соблюдение внутреннего инварианта, тип должен реализовать __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). Этот вызываемый объект позволяет пользователю программно контролировать поведение обновления состояния конкретного объекта, вместо использования статического метода obj объекта. Если не None, этот вызываемый объект будет иметь приоритет над методом __setstate__() объекта.

    Добавлено в версии 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, выглядит так:

copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)

Обработка состояний объектов

Вот пример, демонстрирующий, как изменить поведение сериализации для класса. Класс 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, 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)

Следующий пример считывает полученные данные в формате pickle.

import pickle

with open('data.pickle', 'rb') as f:
    # The protocol version used is detected automatically, so we do not
    # have to specify it.
    data = pickle.load(f)

См. также

Module copyreg

Регистрация конструкторов интерфейса Pickle для типов расширений.

Module pickletools

Инструменты для работы с данными в формате pickle и их анализа.

Module shelve

Индексированные базы данных объектов; использует pickle.

Module copy

Копирование объектов с поверхностной и глубокой копией.

Module marshal

Высокопроизводительная сериализация встроенных типов.

Примечания

1

Не путайте с модулем marshal

2

Вот почему функции lambda не могут быть сериализованы: все функции lambda имеют одинаковое имя: <lambda>.

3

Возникающее исключение, вероятно, будет ImportError или AttributeError, но это может быть что-то другое.

4

Модуль copy использует этот протокол для операций поверхностного и глубокого копирования.

5

Ограничение на буквенно-цифровые символы связано с тем, что идентификаторы сохранения в протоколе 0 разделяются символом новой строки. Поэтому, если в идентификаторах сохранения встречаются символы новой строки, полученный файл pickle станет нечитаемым.

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

Spec-Zone.ru

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