您在这里: 主页 ‣ 深入Python 3 ‣
难度等级: ♦♦♦♦♢
❝ Every Saturday since we’ve lived in this apartment, I have awakened at 6:15, poured myself a bowl of cereal, added
a quarter-cup of 2% milk, sat on this end of this couch, turned on BBC America, and watched Doctor Who. ❞
— Sheldon, The Big Bang Theory
序列化的概念很简单。内存里面有一个数据结构,你希望将它保存下来,重用,或者发送给其他人。你会怎么做?嗯, 这取决于你想要怎么保存,怎么重用,发送给谁。很多游戏允许你在退出的时候保存进度,然后你再次启动的时候回到上次退出的地方。(实际上, 很多非游戏程序也会这么干。) 在这个情况下, 一个捕获了当前进度的数据结构需要在你退出的时候保存到磁盘上,接着在你重新启动的时候从磁盘上加载进来。这个数据只会被创建它的程序使用,不会发送到网络上,也不会被其它程序读取。因此,互操作的问题被限制在保证新版本的程序能够读取以前版本的程序创建的数据。
在这种情况下,pickle 模块是理想的。它是Python标准库的一部分, 所以它总是可用的。它很快; 它的大部分同Python解释器本身一样是用C写的。 它可以存储任意复杂的Python数据结构。
什么东西能用pickle模块存储?
bytes(字节串)对象, 字节数组, 以及 None.
如果这还不够用,pickle模块也是可扩展的。如果你对可扩展性有兴趣,请查看本章最后的进一步阅读小节中的链接。
本章会使用两个Python Shell来讲故事。本章的例子都是一个单独的故事的一部分。当我演示pickle 和 json 模块时,你会被要求在两个Python Shell中来回切换。
为了让事情简单一点,打开Python Shell 并定义下面的变量:
>>> shell = 1
保持该窗口打开。 现在打开另一个Python Shell 并定义下面下面的变量:
>>> shell = 2
贯穿整个章节, 在每个例子中我会使用shell变量来标识使用的是哪个Python Shell。
⁂
pickle模块的工作对象是数据结构。让我们来创建一个:
>>> shell ① 1 >>> entry = {} ② >>> entry['title'] = 'Dive into history, 2009 edition' >>> entry['article_link'] = 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition' >>> entry['comments_link'] = None >>> entry['internal_id'] = b'\xDE\xD5\xB4\xF8' >>> entry['tags'] = ('diveintopython', 'docbook', 'html') >>> entry['published'] = True >>> import time >>> entry['published_date'] = time.strptime('Fri Mar 27 22:20:42 2009') ③ >>> entry['published_date'] time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1)
pickle模块我也想保证里面包含了多种不同的数据类型。不需要太关心这些值。
time 模块包含一个表示时间点(精确到1毫秒)的数据结构(time_struct)以及操作时间结构的函数。strptime()函数接受一个格式化过的字符串并将其转化成一个time_struct。这个字符串使用的是默认格式,但你可以通过格式化代码来控制它。查看time模块来获得更多细节。
这是一个很帅的Python 字典。让我们把它保存到文件。
>>> shell ① 1 >>> import pickle >>> with open('entry.pickle', 'wb') as f: ② ... pickle.dump(entry, f) ③ ...
open() 函数来打开一个文件。设置文件模式为'wb'来以二进制写模式打开文件。把它放入with 语句中来保证在你完成的时候文件自动被关闭。
pickle模块中的dump()函数接受一个可序列化的Python 数据结构, 使用最新版本的pickle协议将其序列化为一个二进制的,Python特定的格式, 并且保存到一个打开的文件里。
最后一句话很重要。
pickle模块接受一个Python数据结构并将其保存的一个文件。
entry.pickle文件做任何有用的事情。
pickle模块序列化。随着新的数据类型被加入到Python语言中,pickle协议已经被修改过很多次了,但是它还是有一些限制。
pickle模块中的函数将使用最新版本的pickle协议。这保证了你对可以被序列化的数据类型有最大的灵活度,但这也意味着生成的文件不能被不支持新版pickle协议的旧版本的Python读取。
⁂
现在切换到你的第二个Python Shell — 即不是你创建entry字典的那个。
>>> shell ① 2 >>> entry ② Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'entry' is not defined >>> import pickle >>> with open('entry.pickle', 'rb') as f: ③ ... entry = pickle.load(f) ④ ... >>> entry ⑤ {'comments_link': None, 'internal_id': b'\xDE\xD5\xB4\xF8', 'title': 'Dive into history, 2009 edition', 'tags': ('diveintopython', 'docbook', 'html'), 'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition', 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1), 'published': True}
entry.pickle文件。pickle模块使用二进制数据格式,所以你总是应该使用二进制模式打开pickle文件。
pickle.load()函数接受一个流对象, 从流中读取序列化后的数据,创建一个新的Python对象,在新的Python对象中重建被序列化的数据,然后返回新建的Python对象。
pickle.dump() / pickle.load()循环的结果是一个和原始数据结构等同的新的数据结构。
>>> shell ① 1 >>> with open('entry.pickle', 'rb') as f: ② ... entry2 = pickle.load(f) ③ ... >>> entry2 == entry ④ True >>> entry2 is entry ④ False >>> entry2['tags'] ⑥ ('diveintopython', 'docbook', 'html') >>> entry2['internal_id'] b'\xDE\xD5\xB4\xF8'
entry.pickle文件。
entry.pickle文件中。现在你从文件中读取序列化后的数据并创建了原始数据结构的一个完美复制品。
'tags'键对应的值是一个元组,而'internal_id'键对应的值是一个bytes对象。原因在这章的后面就会清楚了。
⁂
前一节中的例子展示了如果将一个Python对象序列化到磁盘文件。但如果你不想或不需要文件呢?你也可以序列化到一个内存中的bytes对象。
>>> shell 1 >>> b = pickle.dumps(entry) ① >>> type(b) ② <class 'bytes'> >>> entry3 = pickle.loads(b) ③ >>> entry3 == entry ④ True
pickle.dumps()函数(注意函数名最后的's')执行和pickle.dump()函数相同的序列化。取代接受流对象并将序列化后的数据保存到磁盘文件,这个函数简单的返回序列化的数据。
pickle.dumps()函数返回bytes对象。
pickle.loads()函数(再一次, 注意函数名最后的's') 执行和pickle.load()函数一样的反序列化。取代接受一个流对象并去文件读取序列化后的数据,它接受包含序列化后的数据的bytes对象, 比如pickle.dumps()函数返回的对象。
⁂
pickle协议已经存在好多年了,它随着Python本身的成熟也不断成熟。现在存在四个不同版本 的pickle协议。
bytes 对象和字节数组。它是一个二进制格式。
你看, 字节串和字符串的区别又一次抬起了它们丑陋的头。 (如果你觉得惊奇,你肯定开小差了。) 在实践中这意味着, 尽管Python 3 可以读取版本 2 的pickle 协议生成的数据, Python 2 不能读取版本 3的协议生成的数据.
⁂
pickle 协议是长什么样的呢?让我们离开Python Shell一会会,来看一下我们创建的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协议里面没有明确的版本标志。为了确定保存pickle文件时使用的协议版本,你需要查看序列化后的数据的标记(“opcodes”)并且使用硬编码的哪个版本的协议引入了哪些标记的知识(来确定版本号)。pickle.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
⁂
pickle模块使用的数据格式是Python特定的。它没有做任何兼容其它编程语言的努力。如果跨语言兼容是你的需求之一,你得去寻找其它的序列化格式。一个这样的格式是JSON。 “JSON” 代表 “JavaScript Object Notation,” 但是不要让名字糊弄你。 — JSON 是被设计为跨语言使用的。
Python 3 在标准库中包含了一个 json模块。同 pickle模块类似, json模块包含一些函数,可以序列化数据结构,保存序列化后的数据至磁盘,从磁盘上读取序列化后的数据,将数据反序列化成新的Pythone对象。但两者也有一些很重要的区别。 首先, JSON数据格式是基于文本的, 不是二进制的。RFC 4627 定义了JSON格式以及怎样将各种类型的数据编码成文本。比如,一个布尔值要么存储为5个字符的字符串'false',要么存储为4个字符的字符串 'true'。 所有的JSON值都是大小写敏感的。
第二,由于是文本格式, 存在空白(whitespaces)的问题。 JSON 允许在值之间有任意数目的空白(空格, 跳格, 回车,换行)。空白是“无关紧要的”,这意味着JSON编码器可以按它们的喜好添加任意多或任意少的空白, 而JSON解码器被要求忽略值之间的任意空白。这允许你“美观的打印(pretty-print)” 你的 JSON 数据, 通过不同的缩进层次嵌套值,这样你就可以在标准浏览器或文本编辑器中阅读它。Python 的 json 模块有在编码时执行美观打印(pretty-printing)的选项。
第三, 字符编码的问题是长期存在的。JSON 用纯文本编码数据, 但是你知道, “不存在纯文本这种东西。” JSON必须以Unicode 编码(UTF-32, UTF-16, 或者默认的, UTF-8)方式存储, RFC 4627的第3节 定义了如何区分使用的是哪种编码。
⁂
JSON 看起来非常像你在Javascript中手工定义的数据结构。这不是意外; 实际上你可以使用JavaScript 的eval()函数来“解码” JSON序列化过的数据。(通常的对非信任输入的警告 也适用, 但关键点是JSON 是 合法的JavaScript。) 因此, 你可能已经熟悉JSON了。
>>> shell
1
>>> basic_entry = {} ①
>>> basic_entry['id'] = 256
>>> basic_entry['title'] = 'Dive into history, 2009 edition'
>>> basic_entry['tags'] = ('diveintopython', 'docbook', 'html')
>>> basic_entry['published'] = True
>>> basic_entry['comments_link'] = None
>>> import json
>>> with open('basic.json', mode='w', encoding='utf-8') as f: ②
... json.dump(basic_entry, f) ③
pickle模块一样, json 模块定义了dump()函数,它接受一个Python 数据结构和一个可写的流对象。dump() 函数将Python数据结构序列化并写入到流对象中。在with语句内工作保证当我们完成的时候正确的关闭文件。
那么生成的JSON序列化数据是什么样的呢?
you@localhost:~/diveintopython3/examples$ cat basic.json
{"published": true, "tags": ["diveintopython", "docbook", "html"], "comments_link": null,
"id": 256, "title": "Dive into history, 2009 edition"}
这肯定比pickle 文件更可读。然而 JSON 的值之间可以包含任意数目的空把, 并且json模块提供了一个方便的途径来利用这一点生成更可读的JSON文件。
>>> shell
1
>>> with open('basic-pretty.json', mode='w', encoding='utf-8') as f:
... json.dump(basic_entry, f, indent=2) ①
json.dump()函数传入indent参数, 它以文件变大为代价使生成的JSON文件更可读。indent 参数是一个整数。0 意味着“每个值单独一行。” 大于0的数字意味着“每个值单独一行并且使用这个数目的空格来缩进嵌套的数据结构。”
这是结果:
you@localhost:~/diveintopython3/examples$ cat basic-pretty.json
{
"published": true,
"tags": [
"diveintopython",
"docbook",
"html"
],
"comments_link": null,
"id": 256,
"title": "Dive into history, 2009 edition"
}
⁂
由于JSON 不是Python特定的,对应到Python的数据类型的时候有很多不匹配。有一些仅仅是名字不同,但是有两个Python数据类型完全缺少。看看你能能把它们指出来:
| 笔记 | JSON | Python 3 |
|---|---|---|
| object | dictionary | |
| array | list | |
| string | string | |
| integer | integer | |
| real number | float | |
| * | true
| True
|
| * | false
| False
|
| * | null
| None
|
| * 所有的 JSON 值都是大小写敏感的。 | ||
注意到什么被遗漏了吗?元组和 & 字节串(bytes)! JSON 有数组类型, json 模块将其映射到Python的列表, 但是它没有一个单独的类型对应 “冻结数组(frozen arrays)” (元组)。而且尽管 JSON 非常好的支持字符串,但是它没有对bytes 对象或字节数组的支持。
⁂
即使JSON没有内建的字节流支持, 并不意味着你不能序列化bytes对象。json模块提供了编解码未知数据类型的扩展接口。(“未知”的意思是≴JSON没有定义”。很显然json 模块认识字节数组, 但是它被JSON规范的限制束缚住了。) 如果你希望编码字节串或者其它JSON没有原生支持的数据类型,你需要给这些类型提供定制的编码和解码器。
>>> shell 1 >>> entry ① {'comments_link': None, 'internal_id': b'\xDE\xD5\xB4\xF8', 'title': 'Dive into history, 2009 edition', 'tags': ('diveintopython', 'docbook', 'html'), 'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition', 'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1), 'published': True} >>> import json >>> with open('entry.json', 'w', encoding='utf-8') as f: ② ... json.dump(entry, f) ③ ... Traceback (most recent call last): File "<stdin>", line 5, in <module> File "C:\Python31\lib\json\__init__.py", line 178, in dump for chunk in iterable: File "C:\Python31\lib\json\encoder.py", line 408, in _iterencode for chunk in _iterencode_dict(o, _current_indent_level): File "C:\Python31\lib\json\encoder.py", line 382, in _iterencode_dict for chunk in chunks: File "C:\Python31\lib\json\encoder.py", line 416, in _iterencode o = _default(o) File "C:\Python31\lib\json\encoder.py", line 170, in default raise TypeError(repr(o) + " is not JSON serializable") TypeError: b'\xDE\xD5\xB4\xF8' is not JSON serializable
None值,字符串,字符串元组, bytes对象, 以及time结构体。
情况是这样的: json.dump() 函数试图序列化bytes对象 b'\xDE\xD5\xB4\xF8',但是它失败了,原因是JSON 不支持bytes对象。然而, 如果保存字节串对你来说很重要,你可以定义自己的“迷你序列化格式。”
[download customserializer.py]
def to_json(python_object): ①
if isinstance(python_object, bytes): ②
return {'__class__': 'bytes',
'__value__': list(python_object)} ③
raise TypeError(repr(python_object) + ' is not JSON serializable') ④
json.dump()函数无法自己序列化的实际对象 — 这个例子里是bytes 对象 b'\xDE\xD5\xB4\xF8'。
json.dump()函数传给它的对象的类型。当你的函数只序列化一个类型的时候这不是必须的,但是它使你的函数的覆盖的内容清楚明白,并且在你需要序列化更多类型的时候更容易扩展。
bytes 对象转换成字典。__class__ 键持有原始的数据类型(以字符串的形式, 'bytes'), 而 __value__ 键持有实际的数据。当然它不能是bytes对象; 大体的想法是将其转换成某些可以被JSON序列化的东西! bytes对象就是一个范围在0–255的整数的序列。 我们可以使用list() 函数将bytes对象转换成整数列表。所以b'\xDE\xD5\xB4\xF8' 变成 [222, 213, 180, 248]. (算一下! 这是对的! 16进制的字节 \xDE 是十进制的 222, \xD5 是 213, 以此类推。)
TypeError,那样json.dump() 函数就可以知道你的定制序列化函数不认识该类型。
就这么多;你不需要其它的东西。特别是, 这个定制序列化函数返回Python字典,不是字符串。你不是自己做所有序列化到JSON的工作; 你仅仅在做转换成被支持的类型那部分工作。json.dump() 函数做剩下的事情。
>>> shell 1 >>> import customserializer ① >>> with open('entry.json', 'w', encoding='utf-8') as f: ② ... json.dump(entry, f, default=customserializer.to_json) ③ ... Traceback (most recent call last): File "<stdin>", line 9, in <module> json.dump(entry, f, default=customserializer.to_json) File "C:\Python31\lib\json\__init__.py", line 178, in dump for chunk in iterable: File "C:\Python31\lib\json\encoder.py", line 408, in _iterencode for chunk in _iterencode_dict(o, _current_indent_level): File "C:\Python31\lib\json\encoder.py", line 382, in _iterencode_dict for chunk in chunks: File "C:\Python31\lib\json\encoder.py", line 416, in _iterencode o = _default(o) File "/Users/pilgrim/diveintopython3/examples/customserializer.py", line 12, in to_json raise TypeError(repr(python_object) + ' is not JSON serializable') ④ TypeError: time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1) is not JSON serializable
customserializer 模块是你在前一个例子中定义to_json()函数的地方。
json.dump()函数, 只要将你的函数以default参数传入json.dump()函数。(万岁, Python里一切皆对象!)
json.dump() 函数不再抱怨无法序列化bytes对象了。现在它在抱怨另一个完全不同的对象: time.struct_time 对象。
尽管得到另一个不同的异常看起来不是什么进步, 但它确实是个进步! 再调整一下就可以解决这个问题。
import time
def to_json(python_object):
if isinstance(python_object, time.struct_time): ①
return {'__class__': 'time.asctime',
'__value__': time.asctime(python_object)} ②
if isinstance(python_object, bytes):
return {'__class__': 'bytes',
'__value__': list(python_object)}
raise TypeError(repr(python_object) + ' is not JSON serializable')
customserializer.to_json()函数里面, 我们加入了Python 对象 (json.dump() 处理不了的那些) 是不是 time.struct_time的判断。
bytes对象时类似的事情来转换: 将time.struct_time 结构转化成一个只包含JSON可序列化值的字典。在这个例子里, 最简单的将日期时间转换成JSON可序列化值的方法是使用time.asctime()函数将其转换成字符串。time.asctime() 函数将难看的time.struct_time 转换成字符串 'Fri Mar 27 22:20:42 2009'。
有了两个定制的转换, 整个entry 数据结构序列化到JSON应该没有进一步的问题了。
>>> shell
1
>>> with open('entry.json', 'w', encoding='utf-8') as f:
... json.dump(entry, f, default=customserializer.to_json)
...
you@localhost:~/diveintopython3/examples$ ls -l example.json
-rw-r--r-- 1 you you 391 Aug 3 13:34 entry.json
you@localhost:~/diveintopython3/examples$ cat example.json
{"published_date": {"__class__": "time.asctime", "__value__": "Fri Mar 27 22:20:42 2009"},
"comments_link": null, "internal_id": {"__class__": "bytes", "__value__": [222, 213, 180, 248]},
"tags": ["diveintopython", "docbook", "html"], "title": "Dive into history, 2009 edition",
"article_link": "http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition",
"published": true}
⁂
类似pickle 模块,json模块有一个load()函数接受一个流对象,从中读取 JSON编码过的数据, 并且创建该JSON数据结构的Python对象的镜像。
>>> shell 2 >>> del entry ① >>> entry Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'entry' is not defined >>> import json >>> with open('entry.json', 'r', encoding='utf-8') as f: ... entry = json.load(f) ② ... >>> entry ③ {'comments_link': None, 'internal_id': {'__class__': 'bytes', '__value__': [222, 213, 180, 248]}, 'title': 'Dive into history, 2009 edition', 'tags': ['diveintopython', 'docbook', 'html'], 'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition', 'published_date': {'__class__': 'time.asctime', '__value__': 'Fri Mar 27 22:20:42 2009'}, 'published': True}
pickle模块创建的entry数据结构。
json.load()函数同pickle.load()函数的结果一模一样。你传入一个流对象,它返回一个新的Python对象。
json.load() 函数成功的读取了你在Python Shell #1中创建的entry.json文件并且生成了一个包含那些数据的新的Python对象。接着是坏消息: 它没有重建原始的 entry 数据结构。'internal_id' 和 'published_date' 这两个值被重建为字典 — 具体来说, 你在to_json()转换函数中使用JSON兼容的值创建的字典。
json.load() 并不知道你可能传给json.dump()的任何转换函数的任何信息。你需要的是to_json()函数的逆函数 — 一个接受定制转换出的JSON 对象并将其转换回原始的Python数据类型。
# add this to customserializer.py
def from_json(json_object): ①
if '__class__' in json_object: ②
if json_object['__class__'] == 'time.asctime':
return time.strptime(json_object['__value__']) ③
if json_object['__class__'] == 'bytes':
return bytes(json_object['__value__']) ④
return json_object
to_json()函数创建的'__class__'键。如果是的,'__class__'键对应的值将告诉你如何将值解码成原来的Python数据类型。
time.asctime()函数返回的字符串,你要使用time.strptime()函数。这个函数接受一个格式化过的时间字符串(格式可以自定义,但默认值同time.asctime()函数的默认值相同) 并且返回time.struct_time.
bytes 对象, 你可以使用 bytes() 函数。
就是这样; to_json()函数处理了两种数据类型,现在这两个数据类型也在from_json()函数里面处理了。下面是结果:
>>> shell
2
>>> import customserializer
>>> with open('entry.json', 'r', encoding='utf-8') as f:
... entry = json.load(f, object_hook=customserializer.from_json) ①
...
>>> entry ②
{'comments_link': None,
'internal_id': b'\xDE\xD5\xB4\xF8',
'title': 'Dive into history, 2009 edition',
'tags': ['diveintopython', 'docbook', 'html'],
'article_link': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition',
'published_date': time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),
'published': True}
from_json()函数嵌入到反序列化过程中,把它作为object_hook 参数传入到json.load()函数中。接受函数作为参数的函数; 真方便!
bytes对象的'internal_id'键。它也包含一个'published_date'键,其值为time.struct_time对象。
然而,还有最后一个缺陷。
>>> shell
1
>>> import customserializer
>>> with open('entry.json', 'r', encoding='utf-8') as f:
... entry2 = json.load(f, object_hook=customserializer.from_json)
...
>>> entry2 == entry ①
False
>>> entry['tags'] ②
('diveintopython', 'docbook', 'html')
>>> entry2['tags'] ③
['diveintopython', 'docbook', 'html']
to_json()钩子函数, 也在反序列化过程中加入from_json()钩子函数, 我们仍然没有重新创建原始数据结构的完美复制品。为什么没有?
'tags'键的值为一个三个字符串组成的元组。
'tags' 键的值是一个三个字符串组成的列表。JSON 并不区分元组和列表;它只有一个类似列表的数据类型,数组,并且json模块在序列化过程中会安静的将元组和列表两个都转换成JSON 数组。大多数情况下,你可以忽略元组和列表的区别,但是在使用json 模块时应记得有这么一回使。
☞很多关于
pickle模块的文章提到了cPickle。在Python 2中,pickle模块有两个实现, 一个由纯Python写的而另一个用C写的(但仍然可以在Python中调用)。在Python 3中, 这两个模块已经合并, 所以你总是简单的import pickle就可以。你可能会发现这些文章很有用,但是你应该忽略已过时的关于的cPickle的信息.
使用pickle模块打包:
pickle module
pickle and cPickle — Python object serialization
pickle
使用JSON 和 json 模块:
json — JavaScript Object Notation Serializer
扩展打包:
© 2001–9 Mark Pilgrim