1. 程式人生 > 其它 >控制 Python 類的序列化過程

控制 Python 類的序列化過程

問題

有的類是不支援在多程序間傳遞的,如果非要這麼做,可能會引發奇怪的現象。比如下面這段程式碼:

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 以提供新的拷貝物件