控制 Python 類的序列化過程
阿新 • • 發佈:2021-12-13
問題
有的類是不支援在多程序間傳遞的,如果非要這麼做,可能會引發奇怪的現象。比如下面這段程式碼:
from concurrent.futures import ProcessPoolExecutor, as_completed from pymysql import connect class MySqlDatabase(object): def __init__(self, host='127.0.0.1', port=3306, user='root', pwd='', db=None): self.host = host self.port = port self.user = user self.pwd = pwd self.db = db self.conn = self.connect() def connect(self): return connect(host=self.host, port=self.port, user=self.user, password=self.pwd, database=self.db) def runquery(self, q): with self.conn.cursor() as cur: cur.execute(q) return cur def run_in_pool(db, sql): print(db.runquery(sql)) def run(): quires = ['show tables', 'select * from user'] db = MySqlDatabase(pwd='1234', db='mysql') with ProcessPoolExecutor() as e: fs = [e.submit(run_in_pool, db, q) for q in quires] for f in as_completed(fs): print(f.result()) if __name__ == '__main__': run()
這段程式碼執行後會卡在 for f in as_completed(fs):
這一行,有的平臺會丟擲異常 TypeError: cannot serialize '_io.BufferedReader' object
,有的則什麼都不顯示,這是因為 pymysql
提供的 Connection
物件是不可序列化的,因此多為多程序間的引數傳遞會產生異常。
那麼,如果讓這裡的 MySqlDatabase
例項支援在多程序間傳遞呢?
解決方案
Python 提供了 __getstate__
和 __setstate__
方法以支援類進一步控制其例項的封存過程,這會使的例項可以被 pickle 序列化,正確實現這兩個方法,例項在多程序間就可以正常傳遞了。
以上面的問題為例,更改 MySqlDatabase
實現如下:
class MySqlDatabase(object): ... def __getstate__(self): state = self.__dict__.copy() # 移除不可序列化的屬性 state.pop('conn') return state def __setstate__(self, state): self.__dict__.update(state) # 重新繫結移除的屬性 self.conn = self.connect()
更新後,再次執行,就會得到預期的結果了。
擴充套件
- 如果類定義了
__getstate__()
,它就會被呼叫,其返回的物件是被當做例項內容來封存的,否則封存的是例項的__dict__
- 當解封時,如果類定義了
__setstate__()
,就會在已解封狀態下呼叫它。此時不要求例項的state
物件必須是dict
。沒有定義此方法的話,先前封存的state
物件必須是dict
,且該dict
內容會在解封時賦給新例項的__dict__
- 該方法同樣適用於
copy.copy
以提供新的拷貝物件