您当前位置: 首页 ‣ 潜入 Python 3 ‣
难度等级: ♦♦♦♦♢
❝ 从我们住进这套公寓开始,每周六我都会在 6:15 醒来,给自己倒一碗麦片,加
四分之一杯 2% 牛奶,坐在 **这** 个 **这** 个沙发的角落,打开 BBC 美国频道,观看神秘博士。 ❞
— 谢尔顿,生活大爆炸
从表面上看,序列化 的概念很简单。您在内存中有一个想要保存、重用或发送给其他人的数据结构。您将如何做到这一点?嗯,这取决于您想要如何保存它、如何重用它以及要发送给谁。许多游戏允许您在退出游戏时保存进度,并在重新启动游戏时从上次离开的地方继续。 (实际上,许多非游戏应用程序也这样做。)在这种情况下,捕获“您的进度”的数据结构需要在您退出时存储在磁盘上,然后在您重新启动时从磁盘加载。这些数据仅供创建它的同一个程序使用,永远不会通过网络发送,也不会被创建它的程序以外的任何其他程序读取。因此,互操作性问题仅限于确保程序的更高版本能够读取由早期版本写入的数据。
对于这种情况,pickle
模块是理想的选择。它是 Python 标准库的一部分,因此始终可用。它速度很快;大部分代码是用 C 编写的,就像 Python 解释器本身一样。它可以存储任意复杂的 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
模块包含一个数据结构(struct_time
)来表示一个时间点(精确到毫秒)以及用于操作时间结构的函数。strptime()
函数接受一个格式化的字符串并将其转换为 struct_time
。此字符串采用默认格式,但您可以使用格式代码对其进行控制。有关更多详细信息,请参阅 time
模块。这是一个看起来很漂亮的 Python 字典。让我们将其保存到一个文件中。
>>> shell ① 1 >>> import pickle >>> with open('entry.pickle', 'wb') as f: ② ... pickle.dump(entry, f) ③ ...
open()
函数打开一个文件。将文件模式设置为 'wb'
以 以二进制模式 打开文件以进行写入。将其包装在 with
语句 中,以确保在您完成操作后自动关闭文件。pickle
模块中的 dump()
函数接受一个可序列化的 Python 数据结构,将其序列化为二进制的 Python 专有格式(使用最新版本的 pickle 协议),并将其保存到一个打开的文件中。上一句话非常重要。
pickle
模块接受一个 Python 数据结构并将其保存到一个文件中。entry.pickle
文件在 Perl、PHP、Java 或任何其他语言中执行任何有用的操作。pickle
模块序列化。随着 Python 语言添加了新的数据类型,pickle 协议已经多次发生变化,但仍然存在一些限制。pickle
模块中的函数将使用最新版本的 pickle 协议。这样可以确保您在可以序列化的数据类型方面拥有最大的灵活性,但也意味着生成的 pickle 文件将无法被不支持最新版本的 pickle 协议的旧版本 Python 读取。⁂
现在切换到您的第二个 Python Shell — 即 不是您创建 entry
字典的那个 Shell。
>>> 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 对象直接序列化到磁盘上的文件。但是,如果您不想或不需要文件怎么办?您也可以将 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 文件。
⁂
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 文件的协议版本,您需要查看腌制数据中的标记(“操作码”,opcode),并使用对每个版本的 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
⁂
pickle
模块使用的数据格式是 Python 专有的。它没有尝试与其他编程语言兼容。如果跨语言兼容性是您的其中一个要求,那么您需要查看其他序列化格式。其中一种格式是 JSON。“JSON”代表“JavaScript 对象表示法”,但不要被这个名字误导 — JSON 明确设计为可在多种编程语言之间使用。
Python 3 在标准库中包含一个 json
模块。与 pickle
模块类似,json
模块也包含用于序列化数据结构、将序列化数据存储到磁盘、从磁盘加载序列化数据以及将数据反序列化回新的 Python 对象的函数。但它们之间也存在一些重要差异。首先,JSON 数据格式是基于文本的,而不是二进制的。RFC 4627 定义了 JSON 格式以及不同类型的数据如何以文本形式编码。例如,布尔值存储为五个字符的字符串 'false'
或四个字符的字符串 'true'
。所有 JSON 值都区分大小写。
其次,与任何基于文本的格式一样,都会出现空格问题。 JSON 允许在值之间使用任意数量的空格(空格、制表符、回车符和换行符)。这些空格是“无关紧要的”,这意味着 JSON 编码器可以添加任意数量的空格,而 JSON 解码器必须忽略值之间的空格。这使你能够“漂亮打印”你的 JSON 数据,将值在不同缩进级别的嵌套中很好地排列,以便你可以在标准浏览器或文本编辑器中阅读。Python 的 json
模块提供了在编码期间进行漂亮打印的选项。
第三,存在字符编码的持久问题。 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()
函数,它会使生成的 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 值都区分大小写。 |
你注意到哪些缺失了吗?元组 & 字节! JSON 具有一个数组类型,json
模块将其映射到 Python 列表,但它没有为“冻结数组”(元组)定义单独的类型。虽然 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
对象。但是,如果存储字节对你很重要,你可以定义自己的“迷你序列化格式”。
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()
函数传递给它的 Python 对象的类型。如果你的函数只序列化一种数据类型,这并不是严格必要的,但它可以清楚地表明你的函数涵盖了哪种情况,并且如果以后需要添加更多数据类型的序列化,它会更容易扩展。bytes
对象转换为字典。__class__
键将保存原始数据类型(作为字符串 'bytes'
),__value__
键将保存实际值。当然,它不能是 bytes
对象;整个要点是将其转换为可以以 JSON 格式序列化的东西!bytes
对象只是一系列整数;每个整数都在 0–255 的范围内。我们可以使用 list()
函数将 bytes
对象转换为整数列表。因此,b'\xDE\xD5\xB4\xF8'
变为 [222, 213, 180, 248]
。(计算一下!它可以工作!十六进制字节 \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()
函数,将你的函数传递到 json.dump()
函数的 default 参数中。(万岁,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 编码的数据,并创建一个新的 Python 对象,该对象反映了 JSON 数据结构。
>>> 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()
函数。函数接收函数;太方便了!'internal_id'
键,其值为 bytes
对象。它还包含一个'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
模块pickle
和 cPickle
— Python 对象序列化pickle
关于 JSON 和 json
模块
json
— JavaScript 对象表示法序列化器关于 pickle 扩展性
© 2001–11 Mark Pilgrim