weakref — 你所不知道的 Python 標準函式庫用法 04

weakref – Weak References

The weakref module allows the Python programmer to create weak references to objects.

官方介紹文件:8.8. weakref — Weak references

weakref 函式庫是用來建立對物件的弱引用 (weak reference)。弱引用並不能保證被引用的物件能存活而不受 garbage collector 回收。弱引用通常會使用在 circular reference、caches 或是需要存放大型物件的地方 (上一篇文章談到的 abc 就有使用 WeakSet 來存放 Class attributes)。

01. Quickstart Tutorial

要理解前面一句話,你必須要先了解,在 CPython 中是對每個物件 (還記得 Everytething in Python is Object 嗎?) 採用 reference count 這樣的方式來達成記憶體管理。當物件 A 被建立的時候,其 reference count 會被設定為 1,如果有其他物件 B 引用 A 本身,便會建立強引用 (Strong Reference),讓 A 的 reference count + 1。如果引用的物件被刪除,則會讓 A 的 reference count – 1。

從輸出結果我們可以觀察到,最一開始 A 的 reference count 是 2,當 B = A 之後變成了 3,接著是 6。原因是因為 A 被 B、C、D、E 強引用,因此 A 的 reference count 總共加上了 4 (四個物件來強引用)。而當使用 del 刪除 E,便會把被強引用的 reference count -1。

我們接著使用 weakref.ref() 來建立弱引用,同樣使用 sys.getrefcount() 來觀察物件 reference count 的狀況:

觀察輸出我們可以發現,使用 weakref.ref() 建立弱引用,物件 A 的 reference count 沒有變化,依然還是 2。

我們可以透過呼叫弱引用的物件來建立臨時的強引用:

在 Python 裡面,當 garbage collector 開啟的狀況下,如果一個物件的 reference count 變成 0,GC 就會自動回收該物件。我們可以透過 weakref 來觀察這個現象,從這邊可以觀察到 弱引用不能防止被引用的物件不被 GC 回收 這件事情:

強引用就可以 (很直觀,因為 reference count > 0):

除了透過 weakref.ref() 來建立弱引用,更常使用的是 weakref.proxy(obj),與 weakref.ref 的差異在於,weakref.proxy 回傳的物件直接是可用的 proxy object:

除了建立弱引用之外,weakref 還可以透過 finalize 幫物件加上終結時的 callback:

使用 weakref.finalize() 的好處是,它會持續追蹤弱引用的物件直到物件被 collect 或是程式結束時自動終結。

同時我們也可以使用回傳的終結者 (finalizer),直接呼叫 finalizer 會讓該物件被終結。

02. HOW-TO Guide

Circular References

使用 Reference count 就有機會遇到 circular references,我們可以想像,當有兩個物件 A、B 分別強引用到對方的時候,就會出現循環引用的問題。如果我們 destory A 以及 B,會發現 garbage collector 無法回收任何一個物件 (因為 Reference count > 1 !):

想要解決這樣的問題,我們可以透過 weakref 來達成:

Caching Expensive Object

這邊的 expensive 通常指的是會消耗大量記憶體空間。應用的情境像是要儲存多張 BMP 圖檔到 dictionary 中、或是像 abc 一樣把 class 裡面的 abstract method 存放起來。因為這些 Object 使用的空間都很大,如果使用普通的 dict 會導致記憶體空間無法被回收 (dict key 本身就佔用了一個 strong reference ! 這保證了 reference count 一定大於 0),改用 weakref 的話就能讓存放的物件的 reference count 為 0 的時候釋放這些寶貴的空間。

我們可以根據自身需求,選擇 WeakKeyDictionaryWeakValueDictionary 或是 WeakSet 來使用。

請參考:Lib/abc.py

03. Discussions – PEP205 編改

起初 CPython 並沒有 weak references 的功能 (雖然一開始在記憶體管理的部分選用了 reference count 這個粗暴簡單的技術),直到 2001 年由 Fred L. Drake, Jr 提出 PEP205 後才正式的將相關的功能加入標準函式庫以及標準物件中,在此之前已知有四種不同的 weak reference implementation。正式提出 PEP 205 所要解決的問題,便是本文最開頭提到的兩個問題:Object caches 以及 Circular References。

在 Python 實作 weak references 需要解決的問題有二:如何判定 weak references 失效?weak reference 在 Python code 這層該如何使用?

(ㄧ、判定失效

在 PEP205 之前判定 weak references 是否可用的方式很簡單,將弱引用的物件儲存一份強引用副本在資料結構中,當強引用物件的 reference count 來到 1 的時候,就代表只剩下自己引用了這個物件,因此可以判定弱引用失效。這樣做的好處是,不需要改變 Python 任何的記憶體管理方式,同時又能讓所有的物件都能被弱引用。壞處是,這個做法假設對於弱引用的管理能夠在短時間內解決。我們將強引用放在某個資料結構中,就代表我們需要使用 O(N) 的時間掃描過去檢查,這種方式沒有辦法攤銷掉任何一個弱引用物件的檢查時間。同時這個方式會假定程式會固定的去檢查弱引用是否有失效,種種開銷讓函式庫不太會想要使用 weak references。

另一個變通的作法是,當物件被 de-allocation 的時候,會被所有弱引用所發現,同時特殊呼叫給弱引用管理的程式碼告知這件事情。這個方法會需要修改所有可以被弱引用的物件的 tp_dealloc ,同時還需要在物件上加上 handler,以及一個高效的方法來映射 weak references chain。

(二、Weak references 與 Python code

增加一層 layer 並透過某種方式獲得引用的物件,或是加上 proxy 來直接使用。

實現方式:……

可能會用到的 Application:PyGTK+ Binding、Tkinter circular reference…etc

04. References

如果你覺得這篇文章不錯,歡迎打賞 IOTA:MUYIDBBJZHJMSTMQZGHUSSGCKBBKRDKHQRNMLZONXPFYWSBCFOKEYOHBYFZKLVVFDTG9AOSZCBPUDWGTBNXYUDTDOD

3 comments On weakref — 你所不知道的 Python 標準函式庫用法 04

Leave a reply:

Your email address will not be published.