重构对象的问题
当与你自己的类一起工作时,你必须保证类被腌渍出现在读取pickle的进程的命名空间中。只有该实例的数据而不是类定义被腌渍。类名被用于在反腌渍时,找到构造器(constructor)以创建新对象。以此——往一个文件写入一个类的实例为例:
python
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: # 写入流中 for o in data: print 'WRITING: %s (%s)' % (o.name, o.name_backwards) pickle.dump(o, out_s) finally: out_s.close()
在运行时,该脚本创建一个以在命令行指定的参数为名的文件:
python
$ python pickle_dump_to_file_1.py test.dat WRITING: pickle (elkcip) WRITING: cPickle (elkciPc) WRITING: last (tsal)
一个在读取结果腌渍对象失败的简化尝试:
python
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: # 读取数据 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
$ 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'
正确的版本从原脚本中导入 SimpleObject ,可成功运行。
添加:
python
from pickle_dump_to_file_1 import SimpleObject
至导入列表的尾部,接着重新运行该脚本:
python
$ python pickle_load_from_file_2.py test.dat READ: pickle (elkcip) READ: cPickle (elkciPc) READ: last (tsal)
当腌渍有值的数据类型不能被腌渍时(套接字、文件句柄(file handles)、数据库连接等之类的),有一些特别的考虑。因为使用值而不能被腌渍的类,可以定义 __getstate__()
和 __setstate__()
来返回状态(state)的一个子集,才能被腌渍。新式类(New-style classes)也可以定义__getnewargs__()
,该函数应当返回被传递至类内存分配器(the class memory allocator)(C.__new__()
)的参数。使用这些新特性的更多细节,包含在标准库文档中。
环形引用(Circular References)
pickle协议(pickle protocol)自动处理对象间的环形引用,因此,即使是很复杂的对象,你也不用特别为此做什么。考虑下面这个图:
上图虽然包括几个环形引用,但也能以正确的结构腌渍和重新读取(reloaded)。
python
import pickle class Node(object): """ 一个所有结点都可知它所连通的其它结点的简单有向图。 """ def __init__(self, name): self.name = name self.connections = [] return def add_edge(self, node): "创建两个结点之间的一条边。" self.connections.append(node) return def __iter__(self): return iter(self.connections) def preorder_traversal(root, seen=None, parent=None): """产生器(Generator )函数通过一个先根遍历(preorder traversal)生成(yield)边。""" if seen is None: seen = set() yield (parent, root) if root in seen: return seen.add(root) for node in root: for (parent, subnode) in preorder_traversal(node, seen, root): yield (parent, subnode) return def show_edges(root): "打印图中的所有边。" for parent, child in preorder_traversal(root): if not parent: continue print '%5s -> %2s (%s)' % (parent.name, child.name, id(child)) # 创建结点。 root = Node('root') a = Node('a') b = Node('b') c = Node('c') # 添加边。 root.add_edge(a) root.add_edge(b) a.add_edge(b) b.add_edge(a) b.add_edge(c) a.add_edge(a) print 'ORIGINAL GRAPH:' show_edges(root) # 腌渍和反腌渍该图来创建 # 一个结点集合。 dumped = pickle.dumps(root) reloaded = pickle.loads(dumped) print print 'RELOADED GRAPH:' show_edges(reloaded)
重新读取的诸多节点(译者注:对应图中的圆圈)不再是同一个对象,但是节点间的关系保持住了,而且读取的仅仅是带有多个引用的对象的一个拷贝。上面所说的可以通过测试各节点在pickle处理前和之后的id()
值来验证。
python
$ python pickle_cycle.py ORIGINAL GRAPH: root -> a (4299721744) a -> b (4299721808) b -> a (4299721744) b -> c (4299721872) a -> a (4299721744) root -> b (4299721808) RELOADED GRAPH: root -> a (4299722000) a -> b (4299722064) b -> a (4299722000) b -> c (4299722128) a -> a (4299722000) root -> b (4299722064)
原文 pickle and cPickle – Python object serialization - Python Module of the Week 的后半部分。