1. pickle物件串行化
pickle模塊實作了一個演算法可以將任意的Python物件轉換為一系列位元組,這個程序也被稱為串行化物件,可以傳輸或存盤表示物件的位元組流,然后再重新構造來創建有相同性質的新物件,
1.1 編碼和解碼字串中的資料
第一個例子使用dumps()將一個資料結構編碼為一個字串,然后把這個字串列印到控制臺,它使用了一個完全由內置型別構成的資料結構,任何類的實體都可以pickled,如后面的例子所示,
import pickle import pprint data = [{'a': 'A', 'b': 2, 'c': 3.0}] print('DATA:', end=' ') pprint.pprint(data) data_string = pickle.dumps(data) print('PICKLE: {!r}'.format(data_string))
默認的,pickle將以一種二進制格式寫入,在Python 3程式之間共享時這種格式兼容性最好,

資料串行化后,可以寫到一個檔案、套接字、管道或者其他位置,之后可以讀取這個檔案,將資料解除pickled,以便用同樣的值構造一個新物件,
import pickle import pprint data1 = [{'a': 'A', 'b': 2, 'c': 3.0}] print('BEFORE: ', end=' ') pprint.pprint(data1) data1_string = pickle.dumps(data1) data2 = pickle.loads(data1_string) print('AFTER : ', end=' ') pprint.pprint(data2) print('SAME? :', (data1 is data2)) print('EQUAL?:', (data1 == data2))
新構造的物件等于原來的物件,但并不是同一個物件,

1.2 處理流
除了dumps()和loads(),pickle還提供了一些便利函式來處理類似檔案的流,可以向一個流寫多個物件,然后從流讀取這些物件,而無須事先知道要寫多少個物件或者這些物件多大,
import io import pickle class SimpleObject: def __init__(self, name): self.name = name self.name_backwards = name[::-1] return data = [] data.append(SimpleObject('pickle')) data.append(SimpleObject('preserve')) data.append(SimpleObject('last')) # Simulate a file. out_s = io.BytesIO() # Write to the stream for o in data: print('WRITING : {} ({})'.format(o.name, o.name_backwards)) pickle.dump(o, out_s) out_s.flush() # Set up a read-able stream in_s = io.BytesIO(out_s.getvalue()) # Read the data while True: try: o = pickle.load(in_s) except EOFError: break else: print('READ : {} ({})'.format( o.name, o.name_backwards))
這個例子使用兩個BytesIO緩沖區來模擬流,第一個緩沖區接收pickled的物件,它的值被填入第二個緩沖區,load()讀取這個緩沖區,簡單的資料庫格式也可以使用pickle來存盤物件,shelve模塊就是這樣一個實作,

除了存盤資料,pickle對于行程間通信也很方便,例如,os.fork()和os.pipe()可以用來建立作業行程,從一個管道讀取作業指令,并把結果寫至另一個管道,管理作業執行緒池以及發送作業和接收回應的核心代碼可以重用,因為作業和回應物件不必基于一個特定的類,使用管道或套接字時,在轉儲各個物件之后不要忘記重繪輸出,以便將資料通過連接推送到另一端,參見multiprocessing模塊來了解一個可重用的作業執行緒池管理器,
1.3 重構物件的問題
處理定制類時,pickled的類必須出現在讀取pickle的行程所在的命名空間里,只會pickled這個實體的資料,而不是類定義,類名用于查找建構式,以便在解除pickled時參見新物件,下面這個例子將一個類的實體寫至一個檔案,
import pickleclass SimpleObject: def __init__(self, name): self.name = name l = list(name) l.reverse() self.name_backwards = ''.join(l) if __name__ == '__main__': data = [] data.append(SimpleObject('pickle')) data.append(SimpleObject('preserve')) data.append(SimpleObject('last')) with open('Test.py', 'wb') as out_s: for o in data: print('WRITING: {} ({})'.format( o.name, o.name_backwards)) pickle.dump(o, out_s)
運行這個腳本時,會根據作為命令列引數給定的名字來創建一個檔案,

通過簡單的嘗試加載而得到的pickled物件將會失敗,
import pickle with open('Test.py', 'rb') as in_s: while True: try: o = pickle.load(in_s) except EOFError: break else: print('READ: {} ({})'.format( o.name, o.name_backwards))
這個版本失敗的原因在于并沒有SimpleObject類,

修正后的版本從原腳本匯入了SimpleObject,這一次運行會成功,在匯入串列的最后增加了import陳述句后,現在腳本就能找到這個類并構造物件了,
from demo import SimpleObject
現在允許修改后的腳本會生成期望的結果,

1.4 Unpicklable的物件
并不是所有物件都是可pickled的,套接字、檔案句柄、資料庫連接以及其他運行時狀態依賴于作業系統或其他行程的物件,其可能無法用一種有意義的方式保存,如果物件包含不可pickled的屬性,則可以定義__getstate__()和__setstate__()來回傳所pickled實體的狀態的一個子集,
__getstate__()方法必須回傳一個物件,其中包含所pickled物件的內部狀態,表示狀態的一種便利方式是使用字典,不過值可以是任意的可pickled物件,保存狀態,然后再從pickle加載物件時將所保存的狀態傳入__setstate__(),
import pickle class State: def __init__(self, name): self.name = name def __repr__(self): return 'State({!r})'.format(self.__dict__) class MyClass: def __init__(self, name): print('MyClass.__init__({})'.format(name)) self._set_name(name) def _set_name(self, name): self.name = name self.computed = name[::-1] def __repr__(self): return 'MyClass({!r}) (computed={!r})'.format( self.name, self.computed) def __getstate__(self): state = State(self.name) print('__getstate__ -> {!r}'.format(state)) return state def __setstate__(self, state): print('__setstate__({!r})'.format(state)) self._set_name(state.name) inst = MyClass('name here') print('Before:', inst) dumped = pickle.dumps(inst) reloaded = pickle.loads(dumped) print('After:', reloaded)
這個例子使用了一個單獨的State物件來保存MyClass的內部狀態,從pickle加載MyClass的一個實體時,會向__setstate__()傳入一個State實體,用來初始化這個物件,

1.5 回圈參考
pickle協議會自動處理物件之間的回圈參考,所以復雜資料結構不需要任何特殊的處理,
import pickle class Node: """A simple digraph """ def __init__(self, name): self.name = name self.connections = [] def add_edge(self, node): "Create an edge between this node and the other." self.connections.append(node) def __iter__(self): return iter(self.connections) def preorder_traversal(root, seen=None, parent=None): """Generator function to yield the edges in a graph. """ if seen is None: seen = set() yield (parent, root) if root in seen: return seen.add(root) for node in root: recurse = preorder_traversal(node, seen, root) for parent, subnode in recurse: yield (parent, subnode) def show_edges(root): "Print all the edges in the graph." for parent, child in preorder_traversal(root): if not parent: continue print('{:>5} -> {:>2} ({})'.format( parent.name, child.name, id(child))) # Set up the nodes. root = Node('root') a = Node('a') b = Node('b') c = Node('c') # Add edges between them. 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) # Pickle and unpickle the graph to create # a new set of nodes. dumped = pickle.dumps(root) reloaded = pickle.loads(dumped) print('\nRELOADED GRAPH:') show_edges(reloaded)
重新加載的節點并不是同一個物件,但保持了節點之間的關系,而且如果物件有多個參考,那么只會重新加載這個物件的一個副本,要驗證這兩點,可以在通過pickle傳遞節點之前和之后檢查節點的id()值,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/167451.html
標籤:Python
