Автор оригинала: Doug Hellmann.
Цель:
Сериализация объекта
Модуль pickle
реализует алгоритм преобразования произвольного объекта Python в серию байтов. Этот процесс также называется сериализацией объекта. Затем байтовый поток, представляющий объект, может быть передан или сохранен, а затем реконструирован для создания нового объекта с такими же характеристиками.
Предупреждение
В документации для pickle
четко указано, что он не предлагает никаких гарантий безопасности. Фактически, при распаковке данных можно выполнить произвольный код. Будьте осторожны, используя pickle
для межпроцессного взаимодействия или хранения данных, и не доверяйте данным, которые нельзя проверить как безопасные. См. Модуль hmac для примера безопасного способа проверки источника маринованного источника данных.
Кодирование и декодирование данных в строках
В этом первом примере dumps ()
используется для кодирования структуры данных в виде строки, а затем строка выводится на консоль. Он использует структуру данных, состоящую из полностью встроенных типов. Экземпляры любого класса можно мариновать, как будет показано в следующем примере.
pickle_string.py
import pickle import pprint data [{'a': 'A', 'b': 2, 'c': 3.0}] print('DATA:', end' ') pprint.pprint(data) data_string pickle.dumps(data) print('PICKLE: {!r}'.format(data_string))
По умолчанию рассол будет записан в двоичном формате, наиболее совместимом при совместном использовании между программами Python 3.
$ python3 pickle_string.py DATA: [{'a': 'A', 'b': 2, 'c': 3.0}] PICKLE: b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00cq\x02G@\x08\x00 \x00\x00\x00\x00\x00X\x01\x00\x00\x00bq\x03K\x02X\x01\x00\x00\x0 0aq\x04X\x01\x00\x00\x00Aq\x05ua.'
После сериализации данных их можно записать в файл, сокет, канал и т. Д. Позже файл может быть прочитан и данные извлечены для создания нового объекта с теми же значениями.
pickle_unpickle.py
import pickle import pprint data1 [{'a': 'A', 'b': 2, 'c': 3.0}] print('BEFORE: ', end' ') pprint.pprint(data1) data1_string pickle.dumps(data1) data2 pickle.loads(data1_string) print('AFTER : ', end' ') pprint.pprint(data2) print('SAME? :', (data1 is data2)) print('EQUAL?:', (data1 data2))
Новый созданный объект равен оригиналу, но не совпадает с ним.
$ python3 pickle_unpickle.py BEFORE: [{'a': 'A', 'b': 2, 'c': 3.0}] AFTER : [{'a': 'A', 'b': 2, 'c': 3.0}] SAME? : False EQUAL?: True
Работа с потоками
В дополнение к dumps ()
и load ()
, pickle
предоставляет удобные функции для работы с файловыми потоками. Можно записать несколько объектов в поток, а затем прочитать их из потока, не зная заранее, сколько объектов записано или насколько они велики.
pickle_stream.py
import io import pickle import pprint class SimpleObject: def __init__(self, name): self.name name self.name_backwards name[::-1] return data [] data.append(SimpleObject('pickle')) data.append(SimpleObject('preserve')) data.append(SimpleObject('last')) # Simulate a file. out_s io.BytesIO() # Write to the stream for o in data: print('WRITING : {} ({})'.format(o.name, o.name_backwards)) pickle.dump(o, out_s) out_s.flush() # Set up a read-able stream in_s io.BytesIO(out_s.getvalue()) # Read the data while True: try: o pickle.load(in_s) except EOFError: break else: print('READ : {} ({})'.format( o.name, o.name_backwards))
В этом примере моделируются потоки с использованием двух буферов BytesIO
. Первый получает маринованные объекты, а его значение передается второму, откуда выполняется чтение load ()
. В простом формате базы данных можно также использовать соленые огурцы для хранения объектов. Модуль полки – одна из таких реализаций.
$ python3 pickle_stream.py WRITING : pickle (elkcip) WRITING : preserve (evreserp) WRITING : last (tsal) READ : pickle (elkcip) READ : preserve (evreserp) READ : last (tsal)
Помимо хранения данных, соленья удобны для межпроцессного взаимодействия. Например, os.fork ()
и os.pipe ()
можно использовать для создания рабочих процессов, которые читают инструкции задания из одного канала и записывают результаты в другой канал. Основной код для управления пулом рабочих и отправки заданий и получения ответов можно использовать повторно, поскольку объекты задания и ответа не обязательно должны основываться на конкретном классе. При использовании каналов или сокетов не забывайте промывать после сброса каждого объекта, чтобы протолкнуть данные через соединение на другой конец. См. Модуль multiprocessing для многоразового диспетчера пула рабочих.
Проблемы с реконструкцией объектов
При работе с настраиваемыми классами маринованный класс должен появиться в пространстве имен процесса, читающего рассол. Выбираются только данные для экземпляра, а не определение класса. Имя класса используется для поиска конструктора для создания нового объекта при распаковке. В следующем примере экземпляры класса записываются в файл.
pickle_dump_to_file_1.py
import pickle import sys class SimpleObject: def __init__(self, name): self.name name l list(name) l.reverse() self.name_backwards ''.join(l) if __name__ '__main__': data [] data.append(SimpleObject('pickle')) data.append(SimpleObject('preserve')) data.append(SimpleObject('last')) filename sys.argv[1] with open(filename, 'wb') as out_s: for o in data: print('WRITING: {} ({})'.format( o.name, o.name_backwards)) pickle.dump(o, out_s)
При запуске сценарий создает файл на основе имени, указанного в качестве аргумента в командной строке.
$ python3 pickle_dump_to_file_1.py test.dat WRITING: pickle (elkcip) WRITING: preserve (evreserp) WRITING: last (tsal)
Упрощенная попытка загрузить получившиеся маринованные объекты не удалась.
pickle_load_from_file_1.py
import pickle import pprint import sys filename sys.argv[1] with open(filename, 'rb') as in_s: while True: try: o pickle.load(in_s) except EOFError: break else: print('READ: {} ({})'.format( o.name, o.name_backwards))
Эта версия не работает, потому что недоступен класс SimpleObject
.
$ python3 pickle_load_from_file_1.py test.dat Traceback (most recent call last): File "pickle_load_from_file_1.py", line 15, ino = pickle.load(in_s) AttributeError: Can't get attribute 'SimpleObject' on
Исправленная версия, которая импортирует SimpleObject
из исходного скрипта, успешно работает. Добавление этого оператора импорта в конец списка импорта позволяет сценарию найти класс и построить объект.
from pickle_dump_to_file_1 import SimpleObject
Теперь выполнение измененного сценария дает желаемые результаты.
$ python3 pickle_load_from_file_2.py test.dat READ: pickle (elkcip) READ: preserve (evreserp) READ: last (tsal)
Невыбираемые объекты
Не все предметы можно мариновать. Сокеты, дескрипторы файлов, подключения к базам данных и другие объекты, состояние выполнения которых зависит от операционной системы или другого процесса, может не иметь возможности сохранить значимым образом. Объекты, которые имеют атрибуты, не допускающие выбора, могут определять __getstate __ ()
и __setstate __ ()
для возврата подмножества состояния экземпляра, подлежащего маринованию.
Метод __getstate __ ()
должен возвращать объект, содержащий внутреннее состояние объекта. Один удобный способ представить это состояние – использовать словарь, но значение может быть любым выбираемым объектом. Состояние сохраняется и передается в __setstate __ ()
, когда объект загружается из рассола.
pickle_state.py
import pickle class State: def __init__(self, name): self.name name def __repr__(self): return 'State({!r})'.format(self.__dict__) class MyClass: def __init__(self, name): print('MyClass.__init__({})'.format(name)) self._set_name(name) def _set_name(self, name): self.name name self.computed name[::-1] def __repr__(self): return 'MyClass({!r})>{!r})'.format( self.name, self.computed) def __getstate__(self): state State(self.name) print('__getstate__ -> {!r}'.format(state)) return state def __setstate__(self, state): print('__setstate__({!r})'.format(state)) self._set_name(state.name) inst MyClass('name here') print('Before:', inst) dumped pickle.dumps(inst) reloaded pickle.loads(dumped) print('After:', reloaded)
В этом примере используется отдельный объект State
для хранения внутреннего состояния MyClass
. Когда экземпляр MyClass
загружается из рассола, __setstate __ ()
передается экземпляр State
, который он использует для инициализации объекта.
$ python3 pickle_state.py MyClass.__init__(name here) Before: MyClass('name here')) __getstate__ -> State({'name': 'name here'}) __setstate__(State({'name': 'name here'})) After: MyClass('name here'))
Предупреждение
Если возвращаемое значение ложно, то __setstate __ ()
не вызывается, когда объект не выбран.
Циркулярные ссылки
Протокол pickle автоматически обрабатывает циклические ссылки между объектами, поэтому сложные структуры данных не требуют специальной обработки. Рассмотрим ориентированный граф на рисунке. Он включает несколько циклов, но правильную структуру можно протравить, а затем повторно загрузить.
Сбор структуры данных с помощью циклов
pickle_cycle.py
import pickle class Node: """A simple digraph """ def __init__(self, name): self.name name self.connections [] def add_edge(self, node): "Create an edge between this node and the other." self.connections.append(node) def __iter__(self): return iter(self.connections) def preorder_traversal(root, seenNone, parentNone): """Generator function to yield the edges in a graph. """ if seen is None: seen set() yield (parent, root) if root in seen: return seen.add(root) for node in root: recurse preorder_traversal(node, seen, root) for parent, subnode in recurse: yield (parent, subnode) def show_edges(root): "Print all the edges in the graph." for parent, child in preorder_traversal(root): if not parent: continue print('{:>5} -> {:>2} ({})'.format( parent.name, child.name, id(child))) # Set up the nodes. root Node('root') a Node('a') b Node('b') c Node('c') # Add edges between them. root.add_edge(a) root.add_edge(b) a.add_edge(b) b.add_edge(a) b.add_edge(c) a.add_edge(a) print('ORIGINAL GRAPH:') show_edges(root) # Pickle and unpickle the graph to create # a new set of nodes. dumped pickle.dumps(root) reloaded pickle.loads(dumped) print('\nRELOADED GRAPH:') show_edges(reloaded)
Перезагруженные узлы не являются одним и тем же объектом, но связь между узлами сохраняется, и перезагружается только одна копия объекта с несколькими ссылками. Оба этих оператора можно проверить, исследуя значения id ()
для узлов до и после прохождения через pickle.
$ python3 pickle_cycle.py ORIGINAL GRAPH: root -> a (4315798272) a -> b (4315798384) b -> a (4315798272) b -> c (4315799112) a -> a (4315798272) root -> b (4315798384) RELOADED GRAPH: root -> a (4315904096) a -> b (4315904152) b -> a (4315904096) b -> c (4315904208) a -> a (4315904096) root -> b (4315904152)
Смотрите также
- стандартная библиотека документации для pickle
- PEP 3154 – протокол Pickle версии 4
- shelve – модуль
shelve
используетpickle
для хранения данных в базе данных DBM. - Pickle: интересный стековый язык. – Александр Вассалотти