weakref — 弱引用

原始碼: Lib/weakref.py


weakref 模組允許 Python 程式設計師建立物件的弱引用

在下文中,術語被引用物件表示弱引用所引用的物件。

物件的弱引用不足以保持物件的存活:當對被引用物件的唯一剩餘引用是弱引用時,垃圾回收可以自由地銷燬被引用物件,並將其記憶體用於其他用途。但是,在物件實際銷燬之前,即使沒有對其的強引用,弱引用也可能返回該物件。

弱引用的主要用途是實現快取或包含大型物件的對映,在這種情況下,不希望僅僅因為它出現在快取或對映中就保持大型物件存活。

例如,如果你有許多大型二進位制影像物件,你可能希望將每個物件與一個名稱關聯起來。如果使用 Python 字典將名稱對映到影像,或者將影像對映到名稱,那麼影像物件將僅僅因為它們作為字典中的值或鍵而保持存活。WeakKeyDictionaryWeakValueDictionary 類由 weakref 模組提供,它們是一種替代方案,使用弱引用來構建對映,這樣物件不會僅僅因為它們出現在對映物件中而保持存活。例如,如果影像物件是 WeakValueDictionary 中的值,那麼當對該影像物件的最後剩餘引用是弱對映所持有的弱引用時,垃圾回收可以回收該物件,並且其在弱對映中的相應條目將被簡單地刪除。

WeakKeyDictionaryWeakValueDictionary 在其實現中使用弱引用,在弱引用上設定回撥函式,當鍵或值被垃圾回收回收時通知弱字典。WeakSet 實現 set 介面,但就像 WeakKeyDictionary 一樣,它保持對其元素的弱引用。

finalize 提供了一種直接的方式來註冊一個清理函式,該函式在物件被垃圾回收時呼叫。這比在原始弱引用上設定回撥函式更容易使用,因為該模組會自動確保終結器在物件被回收之前保持存活。

大多數程式應該發現使用這些弱容器型別之一或 finalize 就足夠了 – 通常不需要直接建立自己的弱引用。 weakref 模組公開了底層機制,以方便高階使用者使用。

並非所有物件都可以被弱引用。支援弱引用的物件包括類例項、用 Python 編寫的函式(但不是用 C 編寫的函式)、例項方法、集合、凍結集合、某些 檔案物件生成器、型別物件、套接字、陣列、雙端佇列、正則表示式模式物件和程式碼物件。

在 3.2 版本中更改: 添加了對 thread.lock、threading.Lock 和程式碼物件的支援。

一些內建型別(例如 listdict)不直接支援弱引用,但可以透過子類化新增支援。

class Dict(dict):
    pass

obj = Dict(red=1, green=2, blue=3)   # this object is weak referenceable

CPython 實現細節: 其他內建型別(例如 tupleint)即使子類化也不支援弱引用。

可以很容易地使擴充套件型別支援弱引用;請參閱 弱引用支援

當為給定型別定義 __slots__ 時,除非 __slots__ 宣告中的字串序列中也存在 '__weakref__' 字串,否則將停用弱引用支援。 有關詳細資訊,請參閱 __slots__ 文件

class weakref.ref(object[, callback])

返回對 object 的弱引用。如果被引用物件仍然存活,則可以透過呼叫引用物件來檢索原始物件;如果被引用物件不再存活,則呼叫引用物件將導致返回 None。如果提供了 callback 且不為 None,並且返回的 weakref 物件仍然存活,則在物件即將被終結時將呼叫回撥;弱引用物件將作為唯一引數傳遞給回撥;被引用物件將不再可用。

允許為同一個物件構造多個弱引用。為每個弱引用註冊的回撥將從最近註冊的回撥呼叫到最舊註冊的回撥。

回撥引發的異常將在標準錯誤輸出中註明,但無法傳播;它們的處理方式與從物件的 __del__() 方法引發的異常完全相同。

如果 object 是可雜湊的,則弱引用是可雜湊的。即使在 object 被刪除後,它們也會保持其雜湊值。如果僅在 object 被刪除後才首次呼叫 hash(),則該呼叫將引發 TypeError

弱引用支援相等性測試,但不支援排序。如果被引用物件仍然存活,則兩個引用具有與其被引用物件相同的相等關係(與 callback 無關)。如果任一被引用物件已被刪除,則僅當引用物件是同一物件時,引用才相等。

這是一個可子類化的型別,而不是工廠函式。

__callback__

此只讀屬性返回當前與弱引用關聯的回撥函式。如果不存在回撥函式,或者弱引用所引用的物件不再存活,則此屬性的值將為 None

3.4 版本更改: 添加了 __callback__ 屬性。

weakref.proxy(object[, callback])

返回一個使用弱引用指向 *object* 的代理。 這支援在大多數情況下使用代理,而無需像使用弱引用物件那樣進行顯式解引用。 返回的物件的型別將為 ProxyTypeCallableProxyType,具體取決於 *object* 是否可呼叫。 無論被引用物件如何,代理物件都不可 雜湊; 這避免了許多與其基本可變性質相關的問題,並阻止了它們用作字典鍵。 *callback* 與 ref() 函式的同名引數相同。

在引用物件被垃圾回收後訪問代理物件的屬性會引發 ReferenceError

在 3.8 版本中變更: 擴充套件了代理物件上的運算子支援,使其包含矩陣乘法運算子 @@=

weakref.getweakrefcount(object)

返回指向 *object* 的弱引用和代理的數量。

weakref.getweakrefs(object)

返回指向 *object* 的所有弱引用和代理物件的列表。

class weakref.WeakKeyDictionary([dict])

一個對映類,它弱引用鍵。 當不再存在對鍵的強引用時,字典中的條目將被丟棄。 這可用於將其他資料與應用程式其他部分擁有的物件關聯,而無需向這些物件新增屬性。 這對於覆蓋屬性訪問的物件特別有用。

請注意,當將具有與現有鍵相等的值(但不相等的標識)的鍵插入到字典中時,它會替換該值,但不會替換現有鍵。 因此,當刪除對原始鍵的引用時,它也會刪除字典中的條目

>>> class T(str): pass
...
>>> k1, k2 = T(), T()
>>> d = weakref.WeakKeyDictionary()
>>> d[k1] = 1   # d = {k1: 1}
>>> d[k2] = 2   # d = {k1: 2}
>>> del k1      # d = {}

一種解決方法是在重新賦值之前刪除鍵

>>> class T(str): pass
...
>>> k1, k2 = T(), T()
>>> d = weakref.WeakKeyDictionary()
>>> d[k1] = 1   # d = {k1: 1}
>>> del d[k1]
>>> d[k2] = 2   # d = {k2: 2}
>>> del k1      # d = {k2: 2}

在 3.9 版本中變更: 增加了對 ||= 運算子的支援,如 PEP 584 中所指定的。

WeakKeyDictionary 物件具有一個額外的公開內部引用的方法。 這些引用不能保證在使用時是“活動的”,因此需要在呼叫這些引用之前檢查其結果。 這可用於避免建立導致垃圾收集器使鍵的保留時間長於需要的引用。

WeakKeyDictionary.keyrefs()

返回對鍵的弱引用的可迭代物件。

class weakref.WeakValueDictionary([dict])

一個對映類,它弱引用值。 當不再存在對值的強引用時,字典中的條目將被丟棄。

在 3.9 版本中變更: 增加了對 ||= 運算子的支援,如 PEP 584 中所指定的。

WeakValueDictionary 物件有一個額外的方法,它具有與 WeakKeyDictionary.keyrefs() 方法相同的問題。

WeakValueDictionary.valuerefs()

返回對值的弱引用的可迭代物件。

class weakref.WeakSet([elements])

一個集合類,它儲存對其元素的弱引用。 當不再存在對其的強引用時,將丟棄元素。

class weakref.WeakMethod(method[, callback])

一個自定義的 ref 子類,用於模擬對繫結方法(即在類上定義並在例項上查詢的方法)的弱引用。 由於繫結方法是臨時的,因此標準弱引用無法保持對它的引用。 WeakMethod 具有特殊程式碼來重新建立繫結方法,直到物件或原始函式消亡

>>> class C:
...     def method(self):
...         print("method called!")
...
>>> c = C()
>>> r = weakref.ref(c.method)
>>> r()
>>> r = weakref.WeakMethod(c.method)
>>> r()
<bound method C.method of <__main__.C object at 0x7fc859830220>>
>>> r()()
method called!
>>> del c
>>> gc.collect()
0
>>> r()
>>>

*callback* 與 ref() 函式的同名引數相同。

在 3.4 版本中加入。

class weakref.finalize(obj, func, /, *args, **kwargs)

返回一個可呼叫的終結器物件,該物件將在 *obj* 被垃圾回收時呼叫。 與普通的弱引用不同,終結器將始終存活到引用物件被收集為止,從而大大簡化生命週期管理。

終結器在被呼叫(顯式呼叫或在垃圾收集時呼叫)之前被認為是*活動的*,之後它就變成了*死的*。 呼叫一個活動的終結器將返回求值 func(*arg, **kwargs) 的結果,而呼叫一個死的終結器將返回 None

在垃圾回收期間由終結器回撥引發的異常將在標準錯誤輸出中顯示,但無法傳播。 它們的處理方式與從物件的 __del__() 方法或弱引用的回撥引發的異常相同。

當程式退出時,將呼叫每個剩餘的活動終結器,除非其 atexit 屬性設定為 false。 它們將按照建立的反向順序呼叫。

當模組全域性變數很可能已被替換為 None 時,終結器永遠不會在 直譯器關閉 的後期呼叫其回撥。

__call__()

如果 self 處於活動狀態,則將其標記為死亡狀態,並返回呼叫 func(*args, **kwargs) 的結果。如果 self 處於死亡狀態,則返回 None

detach()

如果 self 處於活動狀態,則將其標記為死亡狀態,並返回元組 (obj, func, args, kwargs)。如果 self 處於死亡狀態,則返回 None

peek()

如果 self 處於活動狀態,則返回元組 (obj, func, args, kwargs)。如果 self 處於死亡狀態,則返回 None

alive

如果 finalizer 處於活動狀態則為 True 的屬性,否則為 False。

atexit

一個可寫的布林屬性,預設值為 True。當程式退出時,它會呼叫所有剩餘的、atexit 為 True 的活動 finalizer。它們的呼叫順序與建立順序相反。

注意

務必確保 funcargskwargs 不擁有對 obj 的任何引用,無論是直接引用還是間接引用,否則 obj 將永遠不會被垃圾回收。特別是,func 不應是 obj 的繫結方法。

在 3.4 版本中加入。

weakref.ReferenceType

弱引用物件的型別物件。

weakref.ProxyType

不可呼叫物件的代理的型別物件。

weakref.CallableProxyType

可呼叫物件的代理的型別物件。

weakref.ProxyTypes

包含所有代理的型別物件的序列。這可以更輕鬆地測試物件是否為代理,而無需依賴於命名這兩種代理型別。

另請參閱

PEP 205 - 弱引用

該功能的提案和理由,包括指向早期實現和有關其他語言中類似功能的資訊的連結。

弱引用物件

弱引用物件沒有方法和屬性,除了 ref.__callback__ 之外。弱引用物件允許透過呼叫它來獲取引用物件,如果它仍然存在的話。

>>> import weakref
>>> class Object:
...     pass
...
>>> o = Object()
>>> r = weakref.ref(o)
>>> o2 = r()
>>> o is o2
True

如果引用物件不再存在,則呼叫引用物件將返回 None

>>> del o, o2
>>> print(r())
None

應該使用表示式 ref() is not None 來測試弱引用物件是否仍然處於活動狀態。通常,需要使用引用物件的應用程式程式碼應遵循此模式

# r is a weak reference object
o = r()
if o is None:
    # referent has been garbage collected
    print("Object has been deallocated; can't frobnicate.")
else:
    print("Object is still live!")
    o.do_something_useful()

線上程應用程式中,對“活動狀態”使用單獨的測試會建立競爭條件;另一個執行緒可能會在呼叫弱引用之前導致弱引用失效;上面顯示的習慣用法線上程應用程式以及單執行緒應用程式中都是安全的。

可以透過子類化來建立 ref 物件的專門版本。這在 WeakValueDictionary 的實現中使用,以減少對映中每個條目的記憶體開銷。這可能最有用的是將附加資訊與引用關聯起來,但也可能用於在呼叫以檢索引用物件時插入額外的處理。

此示例顯示瞭如何使用 ref 的子類來儲存有關物件的附加資訊,並影響訪問引用物件時返回的值

import weakref

class ExtendedRef(weakref.ref):
    def __init__(self, ob, callback=None, /, **annotations):
        super().__init__(ob, callback)
        self.__counter = 0
        for k, v in annotations.items():
            setattr(self, k, v)

    def __call__(self):
        """Return a pair containing the referent and the number of
        times the reference has been called.
        """
        ob = super().__call__()
        if ob is not None:
            self.__counter += 1
            ob = (ob, self.__counter)
        return ob

示例

這個簡單的示例展示了應用程式如何使用物件 ID 來檢索它以前見過的物件。然後,可以在其他資料結構中使用物件的 ID,而無需強制物件保持活動狀態,但如果物件確實需要,仍然可以透過 ID 檢索物件。

import weakref

_id2obj_dict = weakref.WeakValueDictionary()

def remember(obj):
    oid = id(obj)
    _id2obj_dict[oid] = obj
    return oid

def id2obj(oid):
    return _id2obj_dict[oid]

Finalizer 物件

使用 finalize 的主要好處是,它可以輕鬆註冊回撥,而無需保留返回的 finalizer 物件。例如

>>> import weakref
>>> class Object:
...     pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")  
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!

也可以直接呼叫 finalizer。但是 finalizer 最多會呼叫一次回撥。

>>> def callback(x, y, z):
...     print("CALLBACK")
...     return x + y + z
...
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> assert f.alive
>>> assert f() == 6
CALLBACK
>>> assert not f.alive
>>> f()                     # callback not called because finalizer dead
>>> del obj                 # callback not called because finalizer dead

您可以使用其 detach() 方法來登出 finalizer。這會殺死 finalizer,並返回建立時傳遞給建構函式的引數。

>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()                                           
(<...Object object ...>, <function callback ...>, (1, 2), {'z': 3})
>>> newobj, func, args, kwargs = _
>>> assert not f.alive
>>> assert newobj is obj
>>> assert func(*args, **kwargs) == 6
CALLBACK

除非您將 atexit 屬性設定為 False,否則如果 finalizer 仍然處於活動狀態,則會在程式退出時呼叫它。例如

>>> obj = Object()
>>> weakref.finalize(obj, print, "obj dead or exiting")
<finalize object at ...; for 'Object' at ...>
>>> exit()
obj dead or exiting

比較 finalizer 與 __del__() 方法

假設我們要建立一個類,該類的例項表示臨時目錄。當以下事件的第一個發生時,應刪除目錄及其內容

  • 物件被垃圾回收,

  • 呼叫物件的 remove() 方法,或

  • 程式退出。

我們可以嘗試使用 __del__() 方法來實現該類,如下所示

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()

    def remove(self):
        if self.name is not None:
            shutil.rmtree(self.name)
            self.name = None

    @property
    def removed(self):
        return self.name is None

    def __del__(self):
        self.remove()

從 Python 3.4 開始,__del__() 方法不再阻止迴圈引用被垃圾回收,並且在 直譯器關閉 期間,模組全域性變數不再被強制設定為 None。因此,此程式碼在 CPython 上應該沒有任何問題地工作。

但是,__del__() 方法的處理是眾所周知的特定於實現的,因為它取決於直譯器垃圾回收器實現的內部細節。

一個更健壯的替代方法是定義一個 finalizer,它只引用它需要的特定函式和物件,而不是訪問物件的完整狀態

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)

    def remove(self):
        self._finalizer()

    @property
    def removed(self):
        return not self._finalizer.alive

像這樣定義,我們的 finalizer 僅接收對其適當清理目錄所需詳細資訊的引用。如果物件永遠不會被垃圾回收,則 finalizer 仍將在退出時被呼叫。

基於 weakref 的 finalizer 的另一個優點是,它們可以用於為第三方控制定義的類註冊 finalizer,例如在解除安裝模組時執行程式碼

import weakref, sys
def unloading_module():
    # implicit reference to the module globals from the function body
weakref.finalize(sys.modules[__name__], unloading_module)

注意

如果您在守護執行緒中建立 finalizer 物件,而程式即將退出,則 finalizer 可能不會在退出時被呼叫。但是,在守護執行緒中,atexit.register()try: ... finally: ...with: ... 也不能保證清理的發生。