python基礎進階:關於對象序列化實現和原理


本文的文字及圖片來源於網絡,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理

本文章來自騰訊雲 作者:Python知識大全

想要學習Python?有問題得不到第一時間解決?來看看這里“1039649593”滿足你的需求,資料都已經上傳至文件中,可以自行下載!還有海量最新2020python學習資料。
點擊查看
在這里插入圖片描述

在這里插入圖片描述

字符串的編碼和解碼

第一個示例是使用 dumps() 將一個數據結構編碼為一個字符串,然后將其輸出到控制台。它使用內置類型組成的數據結構,其實任何類的實例都可以被序列化,如后面的例子所示。

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))

 

默認情況下,Python 3 的序列化以兼容的二進制形式進行。

$ python3 pickle_string.py

DATA: [{'a': 'A', 'b': 2, 'c': 3.0}]
PICKLE: b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00cq\x02G@\x08\x00
\x00\x00\x00\x00\x00X\x01\x00\x00\x00bq\x03K\x02X\x01\x00\x00\x0
0aq\x04X\x01\x00\x00\x00Aq\x05ua.'

 

一旦數據被序列化,你就可以把它寫入到文件、socket、管道等等中。之后你可以讀取這個文件,反序列化這些數據來構造具有相同值的新對象。

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))

 

新對象和之前的對象相等,但不是之前的對象。

$ python3 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() ,還提供了非常方便的函數用於操作文件流。支持同時寫多個對象到同一個流中,然后在不知道有多少個對象或不知道它們有多大時,能夠從這個流中讀取到這些對象。

pickle_stream.py

import io
import pickle
import pprint

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'))

# 模擬一個文件
out_s = io.BytesIO()

# 寫入流中
for o in data:
    print('WRITING : {} ({})'.format(o.name, o.name_backwards))
    pickle.dump(o, out_s)
    out_s.flush()

# 設置一個可讀取的流
in_s = io.BytesIO(out_s.getvalue())

# 讀取數據
while True:
    try:
        o = pickle.load(in_s)
    except EOFError:
        break
    else:
        print('READ    : {} ({})'.format(
            o.name, o.name_backwards))

 

這個例子使用兩個 BytesIO 緩沖區來模擬流。一個接收序列化對象,另一個通過 load() 方法讀取第一個的值。一個簡單的數據庫格式也可以使用序列化來存儲對象。 shelve 模塊就是這樣使用的一個范例。

$ python3 pickle_stream.py

WRITING : pickle (elkcip)
WRITING : preserve (evreserp)
WRITING : last (tsal)
READ    : pickle (elkcip)
READ    : preserve (evreserp)
READ    : last (tsal)

 

除了用於存儲數據,序列化在用於內部進程通信時也是非常靈活的。比如,使用 os.fork() 和 os.pipe() ,可以建立一些工作進程,它們從一個管道中讀取任務說明並把結果輸出到另一個管道。操作這些工作池、發送任務和接受返回的核心代碼可以復用,因為任務和返回對象不是一個特殊的類。如果使用管道或者套接字,就不要忘記在序列化每個對象后刷新它們,並通過它們之間的連接將數據推送到另外一端。查看 multiprocessing 模塊構建一個可復用的任務池管理器。

重建對象的問題

在處理自定義類時,你應該保證這些被序列化的類會在進程命名空間出現 只有數據實例才能被序列化,而不能是定義的類。在反序列化時,類的名字被用於尋找構造器以便創建新對象。接下來這個例子,是將一個類實例寫入到文件中。

pickle_dump_to_file_1.py
import pickle
import sys

class 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'))

    filename = sys.argv[1]

    with open(filename, 'wb') as out_s:
        for o in data:
            print('WRITING: {} ({})'.format(
                o.name, o.name_backwards))
            pickle.dump(o, out_s)

 

當我運行這個腳本時,它會創建名為我在命令行中輸入的參數的文件。

$ python3 pickle_dump_to_file_1.py test.dat

WRITING: pickle (elkcip)
WRITING: preserve (evreserp)
WRITING: last (tsal)

 

之后嘗試將剛才的序列化的結果對象裝載進來是失敗的。

pickle_load_from_file_1.py

import pickle
import pprint
import sys

filename = sys.argv[1]

with open(filename, '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_load_from_file_1.py test.dat

Traceback (most recent call last):
  File "pickle_load_from_file_1.py", line 15, in <module>
    o = pickle.load(in_s)
AttributeError: Can't get attribute 'SimpleObject' on <module '_
_main__' from 'pickle_load_from_file_1.py'>

 

下面是正確的版本,它從一開始的腳本中導入了 SimpleObject 類。添加導入語句可以讓該腳本找到類並構建對象。

from pickle_dump_to_file_1 import SimpleObject

 

現在運行修改后的腳本可以得到預期的結果了。

$ python3 pickle_load_from_file_2.py test.dat

READ: pickle (elkcip)
READ: preserve (evreserp)
READ: last (tsal)

 

在這里插入圖片描述
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)

 

在這里插入圖片描述
循環引用
序列化協議會自動處理對象間的循環引用,所以即使復雜的數據結構也不需要去特殊處理。考慮下圖,它包含了多個循環,但正確的結構仍然能被反序列化輸出。

序列化一個循環引用的數據結構

pickle_cycle.py

import pickle

class Node:
    """一個簡單的有向圖
    """
    def __init__(self, name):
        self.name = name
        self.connections = []

    def add_edge(self, node):
         """在這個節點和其他節點間建立一條邊
                 """
        self.connections.append(node)

    def __iter__(self):
        return iter(self.connections)

def preorder_traversal(root, seen=None, parent=None):
    """給一個圖生成邊的生成器函數
    """
    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):
     """打印輸出圖的所有邊
         """
    for parent, child in preorder_traversal(root):
        if not parent:
            continue
        print('{:>5} -> {:>2} ({})'.format(
            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('\nRELOADED GRAPH:')
show_edges(reloaded)

 

經過序列化和反序列化,這些新的有向圖節點對象並不是一開始創建的那些對象,但對象之間的關系保持不變,這可以通過檢查對象 id() 返回的值驗證。

$ python3 pickle_cycle.py

ORIGINAL GRAPH:
 root ->  a (4315798272)
    a ->  b (4315798384)
    b ->  a (4315798272)
    b ->  c (4315799112)
    a ->  a (4315798272)
 root ->  b (4315798384)

RELOADED GRAPH:
 root ->  a (4315904096)
    a ->  b (4315904152)
    b ->  a (4315904096)
    b ->  c (4315904208)
    a ->  a (4315904096)
 root ->  b (4315904152

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM