重構物件的問題
當與你自己的類一起工作時,你必須保證類被醃漬出現在讀取pickle的程式的名稱空間中。只有該例項的資料而不是類定義被醃漬。類名被用於在反醃漬時,找到構造器(constructor)以建立新物件。以此——往一個檔案寫入一個類的例項為例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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() |
在執行時,該指令碼建立一個以在命令列指定的引數為名的檔案:
1 2 3 4 5 6 |
$ python pickle_dump_to_file_1.py test.dat WRITING: pickle (elkcip) WRITING: cPickle (elkciPc) WRITING: last (tsal) |
一個在讀取結果醃漬物件失敗的簡化嘗試:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 類可用:
1 2 3 4 5 6 7 |
$ python pickle_load_from_file_1.py test.dat Traceback (most recent call last): File "pickle_load_from_file_1.py", line 52, in <module> o = pickle.load(in_s) AttributeError: 'module' object has no attribute 'SimpleObject' |
正確的版本從原指令碼中匯入 SimpleObject ,可成功執行。
新增:
1 2 |
from pickle_dump_to_file_1 import SimpleObject |
至匯入列表的尾部,接著重新執行該指令碼:
1 2 3 4 5 6 |
$ 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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
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()
值來驗證。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ 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) |