Содержание страницы
12.1. pickle – сериализация объектов Python¶pickle – Python object serialization
Модуль pickle реализует двоичные протоколы для сериализации и десериализации структуры объектов Python. “Pickling” – это процесс преобразования иерархии объектов Python в поток байтов, а “unpickling” – обратная операция, при которой поток байтов (из двоичного файла или байтоподобного объекта) преобразуется обратно в иерархию объектов. Pickling (и unpickling) также называют «сериализацией», «маршалингом»[1] или «уплощением»; однако во избежание путаницы здесь используются термины «pickling» и «unpickling».
Предупреждение
Модуль pickle небезопасен при работе с ошибочными или вредоносными данными. Категорически запрещается десериализовать (unpickle) данные, полученные из ненадёжного или непроверенного источника.
12.1.1. Взаимосвязь с другими модулями Python¶Relationship to other Python modules
12.1.1.1. Сравнение с marshal¶Comparison with marshal
В Python есть более примитивный модуль сериализации под названием marshal, но в целом pickle всегда должен быть предпочтительным способом сериализации объектов Python. marshal существует в основном для поддержки файлов .pyc.
Модуль pickle отличается от marshal в нескольких важных аспектах:
Модуль pickle отслеживает уже сериализованные объекты, чтобы последующие ссылки на тот же объект не сериализовались повторно. marshal этого не делает.
Это имеет значение как для рекурсивных объектов, так и для совместного использования объектов. Рекурсивные объекты – это объекты, содержащие ссылки на самих себя. Они не обрабатываются модулем marshal; более того, попытка выполнить маршалинг рекурсивных объектов с помощью marshal приведёт к аварийному завершению интерпретатора Python. Совместное использование объектов возникает, когда на один и тот же объект есть несколько ссылок в разных местах сериализуемой иерархии. pickle сохраняет такие объекты только один раз и гарантирует, что все остальные ссылки указывают на главную копию. Совместно используемые объекты остаются совместно используемыми, что может быть очень важно для изменяемых объектов.
marshal нельзя использовать для сериализации пользовательских классов и их экземпляров. pickle может прозрачно сохранять и восстанавливать экземпляры классов, однако определение класса должно быть импортируемым и находиться в том же модуле, что и на момент сохранения объекта.
Формат сериализации marshal не гарантирует переносимости между версиями Python. Поскольку его основная задача – поддержка файлов .pyc, разработчики Python оставляют за собой право изменять формат сериализации способами, несовместимыми с предыдущими версиями, если возникнет необходимость. Формат сериализации pickle гарантированно обратно совместим во всех выпусках Python.
12.1.1.2. Сравнение с json¶Comparison with 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. Формат потока данных¶Data stream format
Формат данных, используемый pickle, является специфичным для Python. Преимущество в том, что нет ограничений, налагаемых внешними стандартами, такими как JSON или XDR (которые не могут представлять разделение указателей); однако это означает, что программы не на Python могут не смочь восстановить сериализованные с помощью pickle объекты Python.
По умолчанию формат данных pickle использует относительно компактное двоичное представление. Если требуется оптимальный размер, можно эффективно сжимать сериализованные данные.
Модуль pickletools содержит инструменты для анализа потоков данных, создаваемых модулем pickle. Исходный код pickletools содержит обширные комментарии о кодах операций, используемых протоколами pickle.
В настоящее время существует 5 различных протоколов, которые можно использовать для сериализации. Чем выше используемый протокол, тем более новая версия Python требуется для чтения созданного pickle.
- Протокол версии 0 – это исходный «человекочитаемый» протокол, обратно совместимый с более ранними версиями Python.
- Протокол версии 1 – это старый двоичный формат, также совместимый с более ранними версиями Python.
- Протокол версии 2 был представлен в Python 2.3. Он обеспечивает гораздо более эффективную сериализацию классов нового стиля. Дополнительную информацию об улучшениях, внесённых протоколом 2, см. в PEP 307.
- Версия протокола 3 была добавлена в Python 3.0. Она имеет явную поддержку объектов bytes и не может быть десериализована (unpickled) в Python 2.x. Это протокол по умолчанию и рекомендуемый протокол, когда требуется совместимость с другими версиями Python 3.
- Протокол версии 4 был добавлен в Python 3.4. Он добавляет поддержку очень больших объектов, сериализацию большего числа типов объектов и некоторые оптимизации формата данных. Обратитесь к PEP 3154 для получения информации об улучшениях, внесённых протоколом 4.
Примечание
Сериализация – это более примитивное понятие, чем персистентность; хотя pickle читает и записывает файловые объекты, он не решает проблему именования персистентных объектов, равно как и (ещё более сложную) проблему одновременного доступа к персистентным объектам. Модуль pickle может преобразовывать сложный объект в поток байтов и обратно в объект с той же внутренней структурой. Самое очевидное применение этих потоков байтов – запись в файл, но их также можно передавать по сети или хранить в базе данных. Модуль shelve предоставляет простой интерфейс для сериализации (pickle и unpickle) объектов в файлах баз данных стиля DBM.
12.1.3. Интерфейс модуля¶Module Interface
Для сериализации иерархии объектов достаточно вызвать функцию dumps(). Аналогично, для десериализации потока данных вызывается функция loads(). Однако если требуется более тонкий контроль над сериализацией и десериализацией, можно создать соответственно объект Pickler или Unpickler.
Модуль pickle предоставляет следующие константы:
- pickle.HIGHEST_PROTOCOL¶
Целое число, наибольшая доступная версия протокола. Это значение может быть передано в качестве аргумента протокол функциям dump() и dumps(), а также конструктору Pickler.
- pickle.DEFAULT_PROTOCOL¶
Целое число, версия протокола по умолчанию протокола, используемая для pickling. Может быть меньше HIGHEST_PROTOCOL. В настоящее время протоколом по умолчанию является 3 – новый протокол, разработанный для Python 3.
Модуль pickle предоставляет следующие функции для упрощения процесса упаковки (pickling):
- pickle.dump(obj, file, protocol=None, *, fix_imports=True)¶
Записывает упакованное (pickled) представление объекта obj в открытый файловый объект file. Это эквивалентно вызову Pickler(file, protocol).dump(obj).
Необязательный аргумент протокол (целое число) указывает упаковщику (pickler) использовать заданный протокол; поддерживаются протоколы от 0 до HIGHEST_PROTOCOL. Если не указан, по умолчанию используется DEFAULT_PROTOCOL. Если указано отрицательное число, выбирается HIGHEST_PROTOCOL.
Аргумент file должен иметь метод write(), принимающий один аргумент типа bytes. Таким образом, это может быть файл на диске, открытый для двоичной записи, экземпляр io.BytesIO или любой другой пользовательский объект, соответствующий этому интерфейсу.
Если fix_imports равен true и протокол меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных pickle был читаем в Python 2.
- pickle.dumps(obj, protocol=None, *, fix_imports=True)¶
Возвращает упакованное (pickled) представление объекта в виде объекта bytes, вместо записи в файл.
Аргументы protocol и fix_imports имеют тот же смысл, что и в dump().
- pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")¶
Читает упакованное представление объекта из открытого файлового объекта file и возвращает восстановленную иерархию объекта, заданную в нём. Это эквивалентно Unpickler(file).load().
Версия протокола 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.
- pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")¶
Читает упакованную иерархию объекта из объекта bytes и возвращает восстановленную иерархию объекта, заданную в нём.
Версия протокола pickle определяется автоматически, поэтому аргумент протокол не требуется. Байты, следующие за представлением объекта в 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 указывает упаковщику, какой протокол использовать; поддерживаются протоколы с 0 по HIGHEST_PROTOCOL. Если не указан, по умолчанию используется DEFAULT_PROTOCOL. Если указано отрицательное число, выбирается HIGHEST_PROTOCOL.
Аргумент file должен иметь метод write(), принимающий один аргумент типа bytes. Таким образом, это может быть файл на диске, открытый для бинарной записи, экземпляр io.BytesIO или любой другой пользовательский объект, удовлетворяющий этому интерфейсу.
Если fix_imports равен true и протокол меньше 3, pickle попытается сопоставить новые имена Python 3 со старыми именами модулей, используемыми в Python 2, чтобы поток данных pickle был читаем в Python 2.
- dump(obj)¶
Записывает представление obj в виде pickle в открытый объект файла, переданный в конструкторе.
- persistent_id(obj)¶
По умолчанию ничего не делает. Этот метод существует, чтобы подкласс мог его переопределить.
Если persistent_id() возвращает None, obj упаковывается обычным образом. Любое другое значение заставляет Pickler выдать это значение как постоянный идентификатор для obj. Смысл этого постоянного идентификатора должен быть определён Unpickler.persistent_load(). Обратите внимание, что значение, возвращаемое persistent_id(), само не может иметь постоянный идентификатор.
См. Постоянство внешних объектов для подробностей и примеров использования.
- dispatch_table¶
Таблица диспетчеризации объекта-упаковщика – это реестр функций приведения, которые можно объявить с помощью copyreg.pickle(). Это отображение, ключами которого являются классы, а значениями – функции приведения. Функция приведения принимает один аргумент соответствующего класса и должна соответствовать тому же интерфейсу, что и метод __reduce__().
По умолчанию объект-упаковщик не имеет атрибута dispatch_table и вместо этого использует глобальную таблицу диспетчеризации, управляемую модулем copyreg. Однако, чтобы настроить упаковку для конкретного упаковщика, можно установить атрибут dispatch_table в объект, подобный словарю. В качестве альтернативы, если подкласс Pickler имеет атрибут dispatch_table, то он будет использоваться как таблица диспетчеризации по умолчанию для экземпляров этого класса.
См. Таблицы диспетчеризации в качестве примеров использования.
Новое в версии 3.3.
- fast¶
Устарело. При установке в истинное значение включает быстрый режим. Быстрый режим отключает использование memo, тем самым ускоряя процесс упаковки за счёт отсутствия лишних кодов операций 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-битные строковые экземпляры как bytes-объекты.
- load()¶
Читает представление объекта в pickle из открытого объекта файла, переданного в конструкторе, и возвращает восстановленную иерархию объектов, указанную в нём. Байты, следующие за представлением объекта в pickle, игнорируются.
- persistent_load(pid)¶
По умолчанию вызывает UnpicklingError.
Если определён, persistent_load() должен вернуть объект, заданный постоянным идентификатором pid. При обнаружении недопустимого постоянного идентификатора должно быть вызвано UnpicklingError.
См. Постоянство внешних объектов для подробностей и примеров использования.
- find_class(module, name)¶
Импортирует модуль module при необходимости и возвращает из него объект с именем name. Аргументы module и name – строки (тип str). Обратите внимание: вопреки своему названию, find_class() также используется для поиска функций.
Подклассы могут переопределить этот метод для получения контроля над тем, объекты какого типа и каким образом могут быть загружены, что потенциально снижает риски безопасности. См. Ограничение глобальных объектов для подробностей.
12.1.4. Какие объекты можно сериализовать и десериализовать?¶What can be pickled and unpickled?
Следующие типы можно сериализовать:
- None, True и False
- целые числа, числа с плавающей запятой, комплексные числа
- строки, байты, байтовые массивы
- кортежи, списки, множества и словари, содержащие только сериализуемые объекты
- функции, определённые на верхнем уровне модуля (с помощью def, а не lambda)
- встроенные функции, определённые на верхнем уровне модуля
- классы, определённые на верхнем уровне модуля
- экземпляры таких классов, у которых __dict__ или результат вызова __getstate__() поддаётся пиклингу (см. раздел Пиклинг экземпляров классов для подробностей).
Попытка запакетить (pickle) непиклируемый объект вызовет исключение PicklingError; при этом в базовый файл может быть уже записано некоторое количество байт. Попытка запакетить сильно рекурсивную структуру данных может превысить максимальную глубину рекурсии – в этом случае будет возбуждено RuntimeError. Можно аккуратно увеличить этот предел с помощью sys.setrecursionlimit().
Обратите внимание, что функции (встроенные и определённые пользователем) сериализуются по «полному» имени, а не по значению. [2] Это означает, что сериализуется только имя функции вместе с именем модуля, в котором она определена. Ни код функции, ни её атрибуты не сериализуются. Таким образом, определяющий модуль должен быть импортируем в среде десериализации, и модуль должен содержать именованный объект, иначе будет возбуждено исключение. [3]
Аналогично, классы пиклятся по именованной ссылке, поэтому в среде распикливания действуют те же ограничения. Обратите внимание, что ни код класса, ни его данные не пиклятся, так что в следующем примере атрибут класса attr не восстанавливается в среде распикливания:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
Эти ограничения объясняют, почему сериализуемые функции и классы должны быть определены на верхнем уровне модуля.
Аналогично, при пиклинге экземпляров классов код и данные самого класса не пиклятся вместе с ними. Пиклятся только данные экземпляра. Это сделано намеренно: можно исправлять ошибки в классе или добавлять методы и при этом загружать объекты, созданные более ранней версией класса. Если предполагается использовать долгоживущие объекты, которые переживут много версий класса, стоит поместить номер версии в объекты, чтобы метод __setstate__() класса мог выполнить подходящие преобразования.
12.1.5. Сериализация экземпляров классов¶Pickling Class Instances
В этом разделе описываются общие механизмы, позволяющие определять, настраивать и контролировать процесс сериализации и десериализации экземпляров классов.
В большинстве случаев для того, чтобы экземпляры можно было пиклить, дополнительный код не требуется. По умолчанию 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__() не принимает аргументов и должен возвращать либо строку, либо, предпочтительно, кортеж (возвращаемый объект часто называют «reduce-значением»).
Если возвращается строка, она должна интерпретироваться как имя глобальной переменной. Это должно быть локальное имя объекта относительно его модуля; модуль pickle ищет объект в пространстве имён модуля. Такое поведение обычно полезно для одиночек (singletons).
Если возвращается кортеж, он должен содержать от двух до пяти элементов. Необязательные элементы можно опустить или указать 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. Сохранение внешних объектов¶Persistence of External Objects
Для обеспечения сохранности объектов модуль pickle поддерживает понятие ссылки на объект, находящийся за пределами потока данных pickle. Такие объекты ссылаются через постоянный идентификатор, который может быть либо строкой из буквенно-цифровых символов (для протокола 0) [5], либо произвольным объектом (для любого более нового протокола).
Разрешение таких постоянных идентификаторов не определяется модулем pickle; он делегирует это разрешение определенным пользователем методам пиклера и анпиклера – persistent_id() и persistent_load() соответственно.
Чтобы упаковать объекты, имеющие внешний постоянный идентификатор, пиклер должен иметь пользовательский метод persistent_id(), который принимает объект в качестве аргумента и возвращает либо None, либо постоянный идентификатор для этого объекта. Когда возвращается None, пиклер просто упаковывает объект как обычно. Когда возвращается строка постоянного идентификатора, пиклер упакует этот объект вместе с маркером, чтобы анпиклер распознал его как постоянный идентификатор.
Чтобы распаковать внешние объекты, анпиклер должен иметь пользовательский метод persistent_load(), который принимает объект постоянного идентификатора и возвращает объект, на который он ссылается.
Вот исчерпывающий пример, показывающий, как постоянный идентификатор может использоваться для упаковки внешних объектов по ссылке.
# Простой пример, показывающий, как с помощью persistent ID можно упаковать внешние объекты по ссылке.
# внешние объекты по ссылке.
import pickle
import sqlite3
from collections import namedtuple
# Простой класс, представляющий запись в нашей базе данных.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Вместо упаковки MemoRecord как обычного экземпляра класса, мы выводим
# постоянный идентификатор.
if isinstance(obj, MemoRecord):
# Здесь наш постоянный идентификатор – это просто кортеж, содержащий метку и ключ, который ссылается на определённую запись в базе данных.
# ключ, который ссылается на определённую запись в базе данных.
return ("MemoRecord", obj.key)
else:
# Этот метод вызывается при каждом обнаружении постоянного идентификатора.
# требуется упаковать как обычно.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# Извлеките запись из базы данных по ссылке и верните её.
# Всегда возбуждает исключение, если невозможно вернуть правильный объект.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Извлеките указанную запись из базы данных и верните её.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Инициализируйте и наполните базу данных.
# Извлеките записи для упаковки.
# по постоянному идентификатору.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Обновите запись для надёжности.
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,))
# Извлеките записи, которые требуется упаковать.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Выводит и нумерует строки в текстовом файле.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Обновите запись, для верности.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Удаляет не подлежащие упаковке записи.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
12.1.5.2. Диспетчерские таблицы¶Dispatch Tables
Если требуется настроить упаковку некоторых классов, не затрагивая другой код, зависящий от упаковки, можно создать упаковщик с частной диспетчерской таблицей.
Глобальная диспетчерская таблица, управляемая модулем 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. Обработка объектов с состоянием¶Handling Stateful Objects
Вот пример, показывающий, как изменить поведение упаковки для класса. Класс TextReader открывает текстовый файл и возвращает номер строки и её содержимое каждый раз при вызове его метода readline(). Если экземпляр TextReader упаковывается, сохраняются все атрибуты, кроме члена файлового объекта. Когда экземпляр распаковывается, файл открывается заново, и чтение продолжается с последней позиции. Для реализации такого поведения используются методы __setstate__() и __getstate__().
class TextReader:
"""Вывести и пронумеровать строки в текстовом файле."""
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):
# Восстанавливает состояние ранее открытого файла. Для этого необходимо открыть его заново и читать, пока не будет восстановлено количество строк.
# Наконец, сохраняет файл.
# метод, чтобы избежать изменения исходного состояния.
state = self.__dict__.copy()
# Для любого другого объекта используйте обычную редукцию.
del state['file']
return state
def __setstate__(self, state):
# Восстановить атрибуты экземпляра (например, filename и lineno).
self.__dict__.update(state)
# Получить дескриптор исходного буферного объекта.
# открыть его заново и читать, пока количество строк не восстановится.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Наконец, сохраните файл.
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. Ограничение глобальных объектов¶Restricting Globals
По умолчанию распаковщик импортирует любой класс или функцию, найденные в данных 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):
# Разрешать только безопасные классы из builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Запрещать всё остальное.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Вспомогательная функция, аналогичная 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. Производительность¶Performance
Последние версии протокола pickle (начиная с протокола 2 и выше) содержат эффективные двоичные кодировки для нескольких распространённых возможностей и встроенных типов. Кроме того, модуль pickle имеет прозрачный оптимизатор, написанный на C.
12.1.8. Примеры¶Examples
Для простейшего кода используйте функции dump() и load().
import pickle
# Произвольный набор объектов, поддерживаемых pickle.
data = {
'a': [1, 2.0, 3, 4+6j],
'b': ("character string", b"byte string"),
'c': set([None, True, False])
}
with open('data.pickle', 'wb') as f:
# Запаковать словарь 'data', используя самый высокий доступный протокол.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
Следующий пример читает полученные сериализованные данные.
import pickle
with open('data.pickle', 'rb') as f:
# Версия используемого протокола определяется автоматически, поэтому
# указывать её не нужно.
data = pickle.load(f)
См. также
- Модуль copyreg
- Регистрация конструктора интерфейса pickle для расширенных типов.
- Модуль pickletools
- Инструменты для работы и анализа сериализованных данных.
- Модуль shelve
- Индексированные базы данных объектов; использует pickle.
- Модуль copy
- Поверхностное и глубокое копирование объектов.
- Модуль marshal
- Высокопроизводительная сериализация встроенных типов.
Сноски
| [1] | Не путайте это с модулем marshal |
| [2] | Вот почему функции lambda не могут быть упакованы: все функции lambda имеют одно и то же имя: <lambda>. |
| [3] | Возникающее исключение, скорее всего, будет ImportError или AttributeError, но может быть и другим. |
| [4] | Модуль copy использует этот протокол для операций поверхностного и глубокого копирования. |
| [5] | Ограничение на алфавитно-цифровые символы связано с тем, что постоянные идентификаторы в протоколе 0 разделяются символом новой строки. Поэтому если в постоянных идентификаторах встречаются какие-либо символы новой строки, результирующий pickle станет нечитаемым. |