任务
你想以某种可以接受的速度序列化和重建Python 数据结构,这些数据既包括基本Python 对象也包括类和实例。
解决方案
如果你不想假设你的数据完全由基本 Python 对象组成,或者需要在不同的 Python 版本之间移植,再或者需要将序列化后的形态作为文本传递,那么最好的序列化数据的方法是 cPickle 模块(pickle 模块是完全用 Python 实现的,而且完全可以替代 cPickle,但问题是它比较慢,除非你没有cPickle用,不然它并不是最好的选择)。举个例子假设你有:
data = {12:'twelve', 'feep':list('ciao'), 1.23:4+5j, (1,2,3):u'wer'}
可以将 data 序列化为文本字符串:
import cPickle
text = cPickle.dumps(data)
或者转化为二进制串,这种选择通常更快而且更节省空间:
bytes = cPickle.dumps(data,2)
现在,可以将text和 bytes按照意愿进行各种处理(比如,通过网络传递,作为BLOB放入数据库中,见 7.10节、7.11节和 7.12节),只要你不修改 text和 bytes 本身。对于bytes 而言,那意味着应该保证其二进制字节序不被改变,对于text 而言,应该保证它的文本结构不被改变,包括换行符。之后,无论在什么计算机体系结构之下,无论用什么版本的 Python,你都可以重新构建出那些数据:
redata1 = cPickle.loads(text)
redata2 = cPickle.loads(bytes)
每个调用创建出来的数据结构都等于原数据。比较特别的是,字典的键在原数据和新建数据中的顺序都是任意的,但这个顺序本身是有意义的,因此也会被保存。无须告诉 cPickle.loads 原先的 dumps 是否使用了文本模式(这是默认的模式,可以由一些很老的 Python 版本读取)或二进制模式(快速目紧凑),loads 可以通过检查参数的内容自行判断。
当你希望将数据写入文件时,可以直接使用cPickle的dump函数,它允许你将多个数据结构一个接一个地写入到同一个文件:
ouf = open('datafile.txt','w')
cPickle.dump(data,ouf)
cPickle.dump('some string',ouf)
cPickle.dump(range(19),ouf)
ouf.close()
一旦你做完这些事,就可以从 datafle.txt 中以相同的顺序一个接一个地恢复原来的数据结构:
inf = open('datafile.txt')
a = cPickle.load(inf)
b = cPickle.load(inf)
c = cPickle.load(inf)
inf.close()
还可以给 cPickle.dump 传递值为2的第三个参数,这相当于告诉 cPickle.dump 以二进制的形式(快速且紧凑)序列化数据,但同时数据文件也必须以二进制模式打开,而不能是默认的文本模式,无论你是想写入或是取出数据。
讨论
Python 提供了几种方法来序列化数据(比如,将数据转化为字节串,存入磁盘或数据库,或者通过网络传递)以及从序列化形式重新构建数据。最好的方式是使用cPickle模块。它有一个纯 Python 的对应物,叫做 pickle(cPickle 模块是用C编写的 Python 扩展),但速度就慢得多了,只有在没有cPickle 的情况下才应该考虑使用它(比如,将Python 移植到容量很小的手机中,必须节省每一个字节,所以只能安装那些完全不可或缺的 Python 标准库的子集)。然而,在任何能够使用 pickle 的地方,都可以将其替换为 cPickle:可以用其中的一个将数据序列化,用另一个恢复并重新构建数据,而不会有任何问题。
cPickle 支持绝大多数的基本数据类型(如字典、列表、元组、数字、字符串)以及它们的各种组合形式,同时也支持类和实例。而对类和实例所做的处理仅仅涉及到数据与代码无关(cPikle 对象并不知道怎样序列化代码对象,这可能是因为在不同的 Python发行版之间的可移植性完全无法保证,因此序列化代码对象意义不大。如果你不需要考虑跨版本问题的话,可参看7.6节介绍的对代码对象的序列化方法)。7.4节还有更多的用 cPickle 处理类和实例的细节。
cPickle 保证了在不同 Python 发行版之间的兼容性,也保证了对于特定计算机体系结构的独立性。即使你升级了你的Python 发行版,通过cPickle处理的序列化数据仍然可以被读取,而且即使在不同的计算机上,这种序列化和反序列化操作也是保证可用的。
cPickle 的 dumps 函数接受任何 Python 数据结构作为参数,并返回一个字符串。如果用2 作为 dumps 的第二个参数,dumps 将返回一个字节串:操作速度会更快,而且产生的串长度会更短。可以给 loads函数传递字符串或者字节串,它都将返回一个Python 数据结构,此数据等同于(=)原来的数据。在 dumps 和 loads 调用之间,可以以任意方式处置那个字符串或字节串,比如通过网络传输,存入数据库并读取,或者加密之后再解密。只要这个串本身的结构没有被改变,loads都能够正确地读取(即使是在不同的平台上,或者用更新的发行的版本)。如果需要用老版本(早于2.3)的Python 处理数据,可考虑用1作为第二个参数:其操作速度会比较慢,产生的结果串也不如用2作参数产生的串紧凑短小,但这个串可以被老版本和最新的 Python,甚至以后的Python版本读取。
当需要将数据存入文件时,可以使用cPickle的dump函数,它接受两个参数:需要处理的数据结构和一个打开的文件对象,或者一个类文件对象。如果文件是以二进制输入输出方式打开的,而不是默认方式(文本模式),可以把2作为它的第三个参数,明确要求使用二进制格式,这样速度更快也更紧凑(或用1作为第三个参数来产生结果串,这样速度不快,要求的空间也更多,但是它能够被很老的,甚至早于 2.3的 Python版本读取)。dump 优于 dumps 的地方在于,通过 dump,可以进行几个调用,一个接一个地将不同的数据结构存入到同一个打开的文件。每个数据结构在存入时还会带有附加的信息,如这个串的长度。因此,当你以后打开此文件读取数据(二进制读取,如果你当初要求以二进制格式存入的话)并反复调用cPickle.load 时,可将文件作为其参数,根据顺序一个接一个地构建出原来的数据结构。而load 的返回值,就像 loads 的返回值一样,是一个完全等同于原数据的新的数据结构。
那些习惯于其他语言和库提供的“序列化”工具的用户可能会问,对于想要序列化和反序列化的对象的大小,pickle会不会有什么限制。答案是:没有。你的计算机的内存可能是唯一的限制,如果你的计算机的内存非常大,pickle 在实践中几乎是没有限制的。