<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
開始討論弱參照( weakref )之前,我們先來看看什麼是弱參照?它到底有什麼作用?
假設我們有一個多執行緒程式,並行處理應用資料:
# 佔用大量資源,建立銷燬成本很高 class Data: def __init__(self, key): pass
應用資料 Data 由一個 key 唯一標識,同一個資料可能被多個執行緒同時存取。由於 Data 需要佔用很多系統資源,建立和消費的成本很高。我們希望 Data 在程式中只維護一個副本,就算被多個執行緒同時存取,也不想重複建立。
為此,我們嘗試設計一個快取中介軟體 Cacher :
import threading # 資料快取 class Cacher: def __init__(self): self.pool = {} self.lock = threading.Lock() def get(self, key): with self.lock: data = self.pool.get(key) if data: return data self.pool[key] = data = Data(key) return data
Cacher 內部用一個 dict 物件來快取已建立的 Data 副本,並提供 get 方法用於獲取應用資料 Data 。get 方法獲取資料時先查快取字典,如果資料已存在,便直接將其返回;如果資料不存在,則建立一個並儲存到字典中。因此,資料首次被建立後就進入快取字典,後續如有其它執行緒同時存取,使用的都是快取中的同一個副本。
感覺非常不錯!但美中不足的是:Cacher 有資源洩露的風險!
因為 Data 一旦被建立後,就儲存在快取字典中,永遠都不會釋放!換句話講,程式的資源比如記憶體,會不斷地增長,最終很有可能會爆掉。因此,我們希望一個資料等所有執行緒都不再存取後,能夠自動釋放。
我們可以在 Cacher 中維護資料的參照次數, get 方法自動累加這個計數。於此同時提供一個 remove 新方法用於釋放資料,它先自減參照次數,並在參照次數降為零時將資料從快取欄位中刪除。
執行緒呼叫 get 方法獲取資料,資料用完後需要呼叫 remove 方法將其釋放。Cacher 相當於自己也實現了一遍參照計數法,這也太麻煩了吧!Python 不是內建了垃圾回收機制嗎?為什麼應用程式還需要自行實現呢?
衝突的主要癥結在於 Cacher 的快取字典:它作為一箇中介軟體,本身並不使用資料物件,因此理論上不應該對資料產生參照。那有什麼黑科技能夠在不產生參照的前提下,找到目標物件嗎?我們知道,賦值都是會產生參照的!
這時,弱參照( weakref )隆重登場了!弱參照是一種特殊的物件,能夠在不產生參照的前提下,關聯目標物件。
# 建立一個資料 >>> d = Data('fasionchan.com') >>> d <__main__.Data object at 0x1018571f0> # 建立一個指向該資料的弱參照 >>> import weakref >>> r = weakref.ref(d) # 呼叫弱參照物件,即可找到指向的物件 >>> r() <__main__.Data object at 0x1018571f0> >>> r() is d True # 刪除臨時變數d,Data物件就沒有其他參照了,它將被回收 >>> del d # 再次呼叫弱參照物件,發現目標Data物件已經不在了(返回None) >>> r()
這樣一來,我們只需將 Cacher 快取字典改成儲存弱參照,問題便迎刃而解!
import threading import weakref # 資料快取 class Cacher: def __init__(self): self.pool = {} self.lock = threading.Lock() def get(self, key): with self.lock: r = self.pool.get(key) if r: data = r() if data: return data data = Data(key) self.pool[key] = weakref.ref(data) return data
由於快取字典只儲存 Data 物件的弱參照,因此 Cacher 不會影響 Data 物件的參照計數。當所有執行緒都用完資料後,參照計數就降為零因而被釋放。
實際上,用字典快取資料物件的做法很常用,為此 weakref 模組還提供了兩種只儲存弱參照的字典物件:
因此,我們的資料快取字典可以採用 weakref.WeakValueDictionary 來實現,它的介面跟普通字典完全一樣。這樣我們不用再自行維護弱參照物件,程式碼邏輯更加簡潔明瞭:
import threading import weakref # 資料快取 class Cacher: def __init__(self): self.pool = weakref.WeakValueDictionary() self.lock = threading.Lock() def get(self, key): with self.lock: data = self.pool.get(key) if data: return data self.pool[key] = data = Data(key) return data
weakref 模組還有很多好用的工具類和工具函數,具體細節請參考官方檔案,這裡不再贅述。
那麼,弱參照到底是何方神聖,為什麼會有如此神奇的魔力呢?接下來,我們一起揭下它的面紗,一睹真容!
>>> d = Data('fasionchan.com') # weakref.ref 是一個內建型別物件 >>> from weakref import ref >>> ref <class 'weakref'> # 呼叫weakref.ref型別物件,建立了一個弱參照範例物件 >>> r = ref(d) >>> r <weakref at 0x1008d5b80; to 'Data' at 0x100873d60>
經過前面章節,我們對閱讀內建物件原始碼已經輕車熟路了,相關原始碼檔案如下:
我們先扒一扒弱參照物件的欄位結構,定義於 Include/weakrefobject.h 標頭檔案中的第 10-41 行:
typedef struct _PyWeakReference PyWeakReference; /* PyWeakReference is the base struct for the Python ReferenceType, ProxyType, * and CallableProxyType. */ #ifndef Py_LIMITED_API struct _PyWeakReference { PyObject_HEAD /* The object to which this is a weak reference, or Py_None if none. * Note that this is a stealth reference: wr_object's refcount is * not incremented to reflect this pointer. */ PyObject *wr_object; /* A callable to invoke when wr_object dies, or NULL if none. */ PyObject *wr_callback; /* A cache for wr_object's hash code. As usual for hashes, this is -1 * if the hash code isn't known yet. */ Py_hash_t hash; /* If wr_object is weakly referenced, wr_object has a doubly-linked NULL- * terminated list of weak references to it. These are the list pointers. * If wr_object goes away, wr_object is set to Py_None, and these pointers * have no meaning then. */ PyWeakReference *wr_prev; PyWeakReference *wr_next; }; #endif
由此可見,PyWeakReference 結構體便是弱參照物件的肉身。它是一個定長物件,除固定頭部外還有 5 個欄位:
結合程式碼中的註釋,我們知道:
由此可見,弱參照的工作原理其實就是設計模式中的 觀察者模式( Observer )。當物件被銷燬,它的所有弱參照物件都得到通知,並被妥善處理。
掌握弱參照的基本原理,足以讓我們將其用好。如果您對原始碼感興趣,還可以再深入研究它的一些實現細節。
前面我們提到,對同一物件的所有弱參照,被組織成一個雙向連結串列,連結串列頭儲存在物件中。由於能夠建立弱參照的物件型別是多種多樣的,很難由一個固定的結構體來表示。因此,Python 在型別物件中提供一個欄位 tp_weaklistoffset ,記錄弱參照連結串列頭指標在範例物件中的偏移量。
由此一來,對於任意物件 o ,我們只需通過 ob_type 欄位找到它的型別物件 t ,再根據 t 中的 tp_weaklistoffset 欄位即可找到物件 o 的弱參照連結串列頭。
Python 在 Include/objimpl.h 標頭檔案中提供了兩個宏定義:
/* Test if a type supports weak references */ #define PyType_SUPPORTS_WEAKREFS(t) ((t)->tp_weaklistoffset > 0) #define PyObject_GET_WEAKREFS_LISTPTR(o) ((PyObject **) (((char *) (o)) + Py_TYPE(o)->tp_weaklistoffset))
我們建立弱參照時,需要呼叫弱參照型別物件 weakref 並將被參照物件 d 作為引數傳進去。弱參照型別物件 weakref 是所有弱參照範例物件的型別,是一個全域性唯一的型別物件,定義在 Objects/weakrefobject.c 中,即:_PyWeakref_RefType(第 350 行)。
根據物件模型中學到的知識,Python 呼叫一個物件時,執行的是其型別物件中的 tp_call 函數。因此,呼叫弱參照型別物件 weakref 時,執行的是 weakref 的型別物件,也就是 type 的 tp_call 函數。tp_call 函數則回過頭來呼叫 weakref 的 tp_new 和 tp_init 函數,其中 tp_new 為範例物件分配記憶體,而 tp_init 則負責初始化範例物件。
回到 Objects/weakrefobject.c 原始檔,可以看到 PyWeakref_RefType 的 tp_new 欄位被初始化成 *weakref___new_* (第 276 行)。該函數的主要處理邏輯如下:
當一個物件被回收後,tp_dealloc 函數將呼叫 PyObject_ClearWeakRefs 函數對它的弱參照進行清理。該函數取出物件的弱參照連結串列,然後逐個遍歷,清理 wr_object 欄位並執行 wr_callback 回撥函數(如有)。具體細節不再展開,有興趣的話可以自行查閱 Objects/weakrefobject.c 中的原始碼,位於 880 行。
好了,經過本節學習,我們徹底掌握了弱參照相關知識。弱參照可以在不產生參照計數的前提下,對目標物件進行管理,常用於框架和中介軟體中。弱參照看起來很神奇,其實設計原理是非常簡單的觀察者模式。弱參照物件建立後便插到一個由目標物件維護的連結串列中,觀察(訂閱)物件的銷燬事件。
到此這篇關於Python中弱參照的神奇用法與原理的文章就介紹到這了,更多相關Python弱參照用法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45