Используем Pickle без файлов

Пример из предыдущей секции показал как сериализовать объект напрямую в файл на диске. Но что если он вам не нужен или вы не хотели использовать файл? Вы можете сериализовать в объект bytes в памяти.

>>> shell
1
>>> b = pickle.dumps(entry) ①
>>> type(b) ②
<class 'bytes'>
>>> entry3 = pickle.loads(b) ③
>>> entry3 == entry ④
True

① Функция pickle.dumps() (обратите внимание на 's' в конце имени функции) делает ту же самую сериализацию что и функция pickle.dump(). Вместо того чтобы принимать на вход поток и писать сериализованные данные на диск, она просто возвращает сериализованные данные

② Поскольку протокол pickle использует двоичный формат данных, функция pickle.dumps() возвращает объект типа bytes.

③ Функция pickle.loads() (снова заметьте 's' в конце имени функции) делает ту же самую десериализацию что и функция pickle.load(). Но вместо того чтобы принимать на вход поток и читать сериализованные данные из файла, она принимает на вход объект типа bytes содержащий сериализованные данные, такие как возвращаемые функцией pickle.dumps()

④ Конечный результат таков же: идеальная копия оригинального словаря.

Байты и строки снова вздымают свои уродливые головы

Протокол pickle существует уже много лет, и он развивался вместе с тем как развивался сам Python. Сейчас существует четыре различных версии протокола pickle.

  • Python 1.x породил две версии протокола, основанный на тексте формат (версия 0) и двоичный формат (версия 1)
  • Python 2.3 ввел новый протокол pickle(версия 2) для того чтобы поддерживать новый функционал в классах Python. Он двоичный.
  • Python 3.0 ввел еще один протокол pickle(версия 3) с полной поддержкой объектов типа bytes и массивов байт. Он так же двоичный.

Вау смотрите, разница между строками и байтами снова вздымает свою уродливую голову. (Если вы удивлены, вы не уделяли достаточно внимания.) На практике это значит, что в то время, как Python 3 может читать данные сохраненные при помощи протокола версии 2, Python 2 не может читать данные сохраненные при помощи протокола версии 3.

Отладка файлов pickle

Как выглядит протокол pickle? Давайте ненадолго отложим консоль python и взглянем в файл entry.pickle, который мы создали. Для не вооруженного взгляда он выглядит как тарабарщина.

you@localhost:~/diveintopython3/examples$ ls -l entry.pickle
-rw-r--r-- 1 you you 358 Aug 3 13:34 entry.pickle
you@localhost:~/diveintopython3/examples$ cat entry.pickle
comments_linkqNXtagsqXdiveintopythonqXdocbookqXhtmlq?qX publishedq?
XlinkXJhttp://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition
q Xpublished_dateq
ctime
struct_time
?qRqXtitleqXDive into history, 2009 editionqu.

Не слишком то полезно. Вы можете видеть строки, но остальные типы данных выглядят как непечатаемые (или как минимум не читаемые) символы. Поля даже не разделены хотя бы табуляцией или пробелами. Это не тот формат, который вы бы захотели отлаживать вручную.

>>> shell
1
>>> import pickletools
>>> with open('entry.pickle', 'rb') as f:
... pickletools.dis(f)
0: \x80 PROTO 3
2: } EMPTY_DICT
3: q BINPUT 0
5: ( MARK
6: X BINUNICODE 'published_date'
25: q BINPUT 1
27: c GLOBAL 'time struct_time'
45: q BINPUT 2
47: ( MARK
48: M BININT2 2009
51: K BININT1 3
53: K BININT1 27
55: K BININT1 22
57: K BININT1 20
59: K BININT1 42
61: K BININT1 4
63: K BININT1 86
65: J BININT -1
70: t TUPLE (MARK at 47)
71: q BINPUT 3
73: } EMPTY_DICT
74: q BINPUT 4
76: \x86 TUPLE2
77: q BINPUT 5
79: R REDUCE
80: q BINPUT 6
82: X BINUNICODE 'comments_link'
100: q BINPUT 7
102: N NONE
103: X BINUNICODE 'internal_id'
119: q BINPUT 8
121: C SHORT_BINBYTES 'ÞÕ´ø'
127: q BINPUT 9
129: X BINUNICODE 'tags'
138: q BINPUT 10
140: X BINUNICODE 'diveintopython'
159: q BINPUT 11
161: X BINUNICODE 'docbook'
173: q BINPUT 12
175: X BINUNICODE 'html'
184: q BINPUT 13
186: \x87 TUPLE3
187: q BINPUT 14
189: X BINUNICODE 'title'
199: q BINPUT 15
201: X BINUNICODE 'Dive into history, 2009 edition'
237: q BINPUT 16
239: X BINUNICODE 'article_link'
256: q BINPUT 17
258: X BINUNICODE 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'
337: q BINPUT 18
339: X BINUNICODE 'published'
353: q BINPUT 19
355: \x88 NEWTRUE
356: u SETITEMS (MARK at 5)
357: . STOP
highest protocol among opcodes = 3

Самая интересная часть информации в дизассемблере находится на последней строке, потому что она включает версию протокола, при помощи которого данный файл был сохранен. Не существует явного маркера протокола pickle. Чтобы определить какую версию протокола использовали для сохранения фала Pickle, вам необходимо заглянуть в маркеры(«opcodes») внутри сохраненных данных и использовать вшитую информацию о том какие маркеры были введены, в какой версии протокола Pickle. Функция pickletools.dis() делает именно это, и она печатает результат в последней строке дизассемблированного вывода. Вот функция, которая возвращает только номер версии, без вывода данных:

import pickletools

def protocol_version(file_object):
maxproto = -1
for opcode, arg, pos in pickletools.genops(file_object):
maxproto = max(maxproto, opcode.proto)
return maxproto

И вот она же в действии:
>>> import pickleversion
>>> with open('entry.pickle', 'rb') as f:
... v = pickleversion.protocol_version(f)
>>> v
3