Python对象序列化
pickle模块可以实现任意的Python对象转换为一系列字节(即序列化对象)的算法. 这些字节流可以被传输或存储, 接着也可以重构为一个和原先对象具有相同特征的新对象.
cPickle模块实现了同样的算法, 但它是用c而不是python. 因此, 它比python实现的快上好几倍, 但是不允许使用者去继承Pickle. 如果继承对于你的使用不是很重要, 那么你大可以使用cPickle.
Note
pickle的文档清晰的表明它不提供安全保证. 所以慎用pickle来作为内部进程通信或者数据存储, 也不要相信那些你不能验证安全性的数据.
第一个pickle示例是将一个数据结构编码为一个字符串, 然后将其输出到控制台.
try:
import cPickle as pickle
except:
import pickle
import pprint
我们首先尝试导入cPickle, 并指定别名为”pickle”. 如果因为某种原因导入pickle失败, 我们则导入由Python实现的pickle模块. 如果cPickle是可用的, 会给我们带来更快的实现, 但如果不可用, 也会有正确的实现.
接下来, 我们定义一个完全由基本类型组成的数据结构. 任何类的实例都可被pickle, 这会在下一个例子中表述. 我选择基本数据类型以便更简单的示范.
data = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'DATA:',
pprint.pprint(data)
现在我们就可以使用pickle.dumps()来创建数据值的字符串表示.
data_string = pickle.dumps(data)
print 'PICKLE:', data_string
默认情况下, pickle仅使用ASCII字符. 也可以使用高效的二进制格式. 但这些示例依然使用了ASCII格式.
$ python pickle_string.py
DATA:[{'a': 'A', 'b': 2, 'c': 3.0}]
PICKLE: (lp1
(dp2
S'a'
S'A'
sS'c'
F3
sS'b'
I2
sa.
一旦数据被序列化, 你就可以把他写入到文件、socket、管道等等中. 之后你可以读取这个文件, unpickle这些数据来构造具有相同值的新对象.
data1 = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'BEFORE:',
pprint.pprint(data1)
data1_string = pickle.dumps(data1)
data2 = pickle.loads(data1_string)
print 'AFTER:',
pprint.pprint(data2)
print 'SAME?:', (data1 is data2)
print 'EQUAL?:', (data1 == data2)
正像你看到的那样, 新构造的对象等于原来的对象, 但他们又不是相同的对象. 这里不足为奇.
$ python pickle_unpickle.py
BEFORE:[{'a': 'A', 'b': 2, 'c': 3.0}]
AFTER:[{'a': 'A', 'b': 2, 'c': 3.0}]
SAME?: False
EQUAL?: True
pickle除了提供dumps()和loads(), 还提供非常方便的函数用于操作类文件流. 支持同时写多个对象到同一个流中, 然后在不知道有多少个对象或不知道它们有多大时, 能够从这个流中读取多个对象也是可能的.
try:
import cPickle as pickle
except:
import pickle
import pprint
from StringIO import StringIO
class SimpleObject(object):
def __init__(self, name):
self.name = name
l = list(name)
l.reverse()
self.name_backwards = ''.join(l)
return
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('cPickle'))
data.append(SimpleObject('last'))
# Simulate a file with StringIO
out_s = StringIO()
# Write to the stream
for o in data:
print 'WRITING: %s (%s)' % (o.name, o.name_backwards)
pickle.dump(o, out_s)
out_s.flush()
# Set up a read-able stream
in_s = StringIO(out_s.getvalue())
# Read the data
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print 'READ: %s (%s)' % (o.name, o.name_backwards)
这个例子使用StringIO缓冲区来模拟流, 因此我们在建立可读流时得玩点小花样. 一个简单数据库格式也可以使用pickle来存储对象, 虽然使用shelve模块可能会更简单.
$ python pickle_stream.py
WRITING: pickle (elkcip)
WRITING: cPickle (elkciPc)
WRITING: last (tsal)
READ: pickle (elkcip)
READ: cPickle (elkciPc)
READ: last (tsal)
除了用于存储数据, pickle在用于内部进程通信时是非常灵活的. 比如, 使用os.fork()和os.pipe(), 可以建立一些工作进程, 它们从一个管道中读取任务说明并把结果输出到另一个管道. 操作这些工作池、发送任务和接受反应的核心代码可以重复利用, 因为任务和反应对象不是一个特殊的类. 如果你使用管道或者sockets, 就不要忘记在dump每个对象后刷新它们并通过其间的连接将数据推送到另外一个进程.
在处理自定义类时, 你应该保证这些被pickled的类会在进程名空间出现. 只有数据实例才能被pickle, 而不能是定义的类. 在unpickle时, 类的名字被用于寻找构造器以便创建新对象. 接下来这个例子, 是将一个类实例写入到文件中:
try:
import cPickle as pickle
except:
import pickle
import sys
class SimpleObject(object):
def __init__(self, name):
self.name = name
l = list(name)
l.reverse()
self.name_backwards = ''.join(l)
return
if __name__ == '__main__':
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('cPickle'))
data.append(SimpleObject('last'))
try:
filename = sys.argv[1]
except IndexError:
raise RuntimeError('Please specify a filename as an argument to %s' % sys.argv[0])
out_s = open(filename, 'wb')
try:
# Write to the stream
for o in data:
print 'WRITING: %s (%s)' % (o.name, o.name_backwards)
pickle.dump(o, out_s)
finally:
out_s.close()
当我运行这个脚本时, 它会创建名为我在命令行中输入的参数的文件:
$ python pickle_dump_to_file_1.py test.dat
WRITING: pickle (elkcip)
WRITING: cPickle (elkciPc)
WRITING: last (tsal)
一个简单的尝试将刚才的pickle结果对象装载进来可以是如下的样子:
try:
import cPickle as pickle
except:
import pickle
import pprint
from StringIO import StringIO
import sys
try:
filename = sys.argv[1]
except IndexError:
raise RuntimeError('Please specify a filename as an argument to %s' % sys.argv[0])
in_s = open(filename, 'rb')
try:
# Read the data
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print 'READ: %s (%s)' % (o.name, o.name_backwards)
finally:
in_s.close()
这个版本失败了, 因为这里没有可用的SimpleObject类.
$ python pickle_load_from_file_1.py test.dat
Traceback (most recent call last):
File "pickle_load_from_file_1.py", line 52, in
o = pickle.load(in_s)
AttributeError: 'module' object has no attribute 'SimpleObject'
一个正确版本, 它从pickle_dump_to_file_1导入了SimpleObject类, 可以成功运行. 增加:
from pickle_dump_to_file_1 import SimpleObject
到导入列表的最后, 然后运行这个脚本:
$ python pickle_load_from_file_2.py test.dat
READ: pickle (elkcip)
READ: cPickle (elkciPc)
READ: last (tsal)
在pickle那些不能被pickle的数据(如sockets、文件句柄、数据库连接等等)时, 需要考虑一些特殊之处. 那些不能被pickle的类可以定义__getstate__()和__setstate__()来返回实例在被pickle时的状态. 新风格的类也可以定义__getnewargs__(), 它返回传递给类内存分配者(C.__new__())的参数. 关于这些特征的更详细的使用描述可以在标准库文档中找到.