1. 程式人生 > 程式設計 >Python3 pickle物件序列化程式碼例項解析

Python3 pickle物件序列化程式碼例項解析

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程式之間共享時這種格式相容性最好。

Python3 pickle物件序列化程式碼例項解析

資料序列化後,可以寫到一個檔案、套接字、管道或者其他位置。之後可以讀取這個檔案,將資料解除pickled,以便用同樣的值構造一個新物件。

import pickle
import pprint
data1 = [{'a': 'A','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))

新構造的物件等於原來的物件,但並不是同一個物件。

Python3 pickle物件序列化程式碼例項解析

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模組就是這樣一個實現。

Python3 pickle物件序列化程式碼例項解析

除了儲存資料,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)

執行這個指令碼時,會根據作為命令列引數給定的名字來建立一個檔案。

Python3 pickle物件序列化程式碼例項解析

通過簡單的嘗試載入而得到的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類。

Python3 pickle物件序列化程式碼例項解析

修正後的版本從原指令碼匯入了SimpleObject,這一次執行會成功。在匯入列表的最後增加了import語句後,現在指令碼就能找到這個類並構造物件了。

from demo import SimpleObject

現在允許修改後的指令碼會生成期望的結果。

Python3 pickle物件序列化程式碼例項解析

1.4Unpicklable的物件

並不是所有物件都是可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例項,用來初始化這個物件。

Python3 pickle物件序列化程式碼例項解析

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()值。

Python3 pickle物件序列化程式碼例項解析

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。