weakref --- 弱引用?

源碼: Lib/weakref.py


weakref 模塊允許Python程序員創(chuàng)建對象的 weak references 。

在下文中,術語 referent 表示由弱引用引用的對象。

對對象的弱引用不能保證對象存活:當對像的引用只剩弱引用時, garbage collection 可以銷毀引用并將其內存重用于其他內容。但是,在實際銷毀對象之前,即使沒有強引用,弱引用也一直能返回該對象。

弱引用的主要用途是實現保存大對象的高速緩存或映射,但又不希望大對象僅僅因為它出現在高速緩存或映射中而保持存活。

例如,如果您有許多大型二進制圖像對象,則可能希望將名稱與每個對象關聯起來。如果您使用Python字典將名稱映射到圖像,或將圖像映射到名稱,則圖像對象將保持活動狀態(tài),因為它們在字典中顯示為值或鍵。 weakref 模塊提供的 WeakKeyDictionaryWeakValueDictionary 類可以替代Python字典,使用弱引用來構造映射,這些映射不會僅僅因為它們出現在映射對象中而使對象保持存活。例如,如果一個圖像對象是 WeakValueDictionary 中的值,那么當對該圖像對象的剩余引用是弱映射對象所持有的弱引用時,垃圾回收可以回收該對象并將其在弱映射對象中相應的條目刪除。

WeakKeyDictionaryWeakValueDictionary 在它們的實現中使用弱引用,在弱引用上設置回調函數,當鍵或值被垃圾回收回收時通知弱字典。 WeakSet 實現了 set 接口,但像 WeakKeyDictionary 一樣,只持有其元素的弱引用。

finalize 提供了注冊一個對象被垃圾收集時要調用的清理函數的方式。這比在原始弱引用上設置回調函數更簡單,因為模塊會自動確保對象被回收前終結器一直保持存活。

這些弱容器類型之一或者 finalize 就是大多數程序所需要的 - 通常不需要直接創(chuàng)建自己的弱引用。weakref 模塊暴露了低級機制,以便于高級用途。

Not all objects can be weakly referenced. Objects which support weak references include class instances, functions written in Python (but not in C), instance methods, sets, frozensets, some file objects, generators, type objects, sockets, arrays, deques, regular expression pattern objects, and code objects.

在 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 implementation detail: 其他內置類型例如 tupleint 不支持弱引用,即使通過子類化也不支持。

Extension types can easily be made to support weak references; see Weak Reference Support.

When __slots__ are defined for a given type, weak reference support is disabled unless a '__weakref__' string is also present in the sequence of strings in the __slots__ declaration. See __slots__ documentation for details.

class weakref.ref(object[, callback])?

返回對 對象 的弱引用。如果原始對象仍然存活,則可以通過調用引用對象來檢索原始對象;如果引用的原始對象不再存在,則調用引用對象將得到 None 。如果提供了 回調 而且值不是 None ,并且返回的弱引用對象仍然存活,則在對象即將終結時將調用回調;弱引用對象將作為回調的唯一參數傳遞;指示物將不再可用。

許多弱引用也允許針對相同對象來構建。 為每個弱引用注冊的回調將按從最近注冊的回調到最早注冊的回調的順序被調用。

回調所引發(fā)的異常將記錄于標準錯誤輸出,但無法被傳播;它們會按與對象的 __del__() 方法所引發(fā)的異常相同的方式被處理。

如果 object 可哈希,則弱引用也為 hashable。 即使在 object 被刪除之后它們仍將保持其哈希值。 如果 hash()object 被刪除之后才首次被調用,則該調用將引發(fā) TypeError。

弱引用支持相等檢測,但不支持排序比較。 如果被引用對象仍然存在,兩個引用具有與它們的被引用對象一致的相等關系(無論 callback 是否相同)。 如果刪除了任一被引用對象,則僅在兩個引用對象為同一對象時兩者才相等。

這是一個可子類化的類型而非一個工廠函數。

__callback__?

這個只讀屬性會返回當前關聯到弱引用的回調。 如果回調不存在或弱引用的被引用對象已不存在,則此屬性的值為 None。

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

weakref.proxy(object[, callback])?

返回 object 的一個使用弱引用的代理。 此函數支持在大多數上下文中使用代理,而不要求顯式地對所使用的弱引用對象解除引用。 返回的對象類型將為 ProxyTypeCallableProxyType,具體取決于 object 是否可調用。 Proxy 對象不是 hashable 對象,無論被引用對象是否可哈希;這可避免與它們的基本可變性質相關的多種問題,并可防止它們被用作字典鍵。 callbackref() 函數的同名形參含義相同。

在 3.8 版更改: 擴展代理對象所支持的運算符,包括矩陣乘法運算符 @@=。

weakref.getweakrefcount(object)?

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

weakref.getweakrefs(object)?

返回由指向 object 的所有弱引用和代理構成的列表。

class weakref.WeakKeyDictionary([dict])?

弱引用鍵的映射類。 當不再存在對鍵的強引用時,字典中的條目將被丟棄。 這可被用來將額外數據關聯到一個應用中其他部分所擁有的對象而無需在那些對象中添加屬性。 這對于重載了屬性訪問的對象來說特別有用。

在 3.9 版更改: 增加了對 ||= 運算符的支持,相關說明見 PEP 584。

WeakKeyDictionary 對象具有一個額外方法可以直接公開內部引用。 這些引用不保證在它們被使用時仍然保持“存活”,因此這些引用的調用結果需要在使用前進行檢測。 此方法可用于避免創(chuàng)建會導致垃圾回收器將保留鍵超出實際需要時長的引用。

WeakKeyDictionary.keyrefs()?

返回包含對鍵的弱引用的可迭代對象。

class weakref.WeakValueDictionary([dict])?

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

在 3.9 版更改: 增加了對 ||= 運算符的支持,相關說明見 PEP 584。

WeakValueDictionary 對象具有一個額外方法,此方法存在與 WeakKeyDictionary 對象的 keyrefs() 方法相同的問題。

WeakValueDictionary.valuerefs()?

返回包含對值的弱引用的可迭代對象。

class weakref.WeakSet([elements])?

保持對其元素弱引用的集合類。 當不再有對某個元素的強引用時元素將被丟棄。

class weakref.WeakMethod(method)?

一個模擬對綁定方法(即在類中定義并在實例中查找的方法)進行弱引用的自定義 ref 子類。 由于綁定方法是臨時性的,標準弱引用無法保持它。 WeakMethod 包含特別代碼用來重新創(chuàng)建綁定方法,直到對象或初始函數被銷毀:

>>>
>>> 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()
>>>

3.4 新版功能.

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

返回一個可調用的終結器對象,該對象將在 obj 作為垃圾回收時被調用。 與普通的弱引用不同,終結器將總是存活,直到引用對象被回收,這極大地簡化了生存期管理。

終結器總是被視為 存活 直到它被調用(顯式調用或在垃圾回收時隱式調用),調用之后它將 死亡。 調用存活的終結器將返回 func(*arg, **kwargs) 的求值結果,而調用死亡的終結器將返回 None

在垃圾收集期間由終結器回調所引發(fā)異常將顯示于標準錯誤輸出,但無法被傳播。 它們會按與對象的 __del__() 方法或弱引用的回調所引發(fā)異常相同的方式被處理。

當程序退出時,剩余的存活終結器會被調用,除非它們的 atexit 屬性已被設為假值。 它們會按與創(chuàng)建時相反的順序被調用。

終結器在 interpreter shutdown 的后期絕不會發(fā)起調用其回調函數,此時模塊全局變量很可能已被替換為 None。

__call__()?

如果 self 為存活狀態(tài)則將其標記為已死亡,并返回調用 func(*args, **kwargs) 的結果。 如果 self 已死亡則返回 None。

detach()?

如果 self 為存活狀態(tài)則將其標記為已死亡,并返回元組 (obj, func, args, kwargs)。 如果 self 已死亡則返 None。

peek()?

如果 self 為存活狀態(tài)則返回元組 (obj, func, args, kwargs)。 如果 self 已死亡則返回 None

alive?

如果終結器為存活狀態(tài)則該特征屬性為真值,否則為假值。

atexit?

一個可寫的布爾型特征屬性,默認為真值。 當程序退出時,它會調用所有 atexit 為真值的剩余存活終結器。 它們會按與創(chuàng)建時相反的順序被調用。

備注

很重要的一點是確保 func, argskwargs 不擁有任何對 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 對象可以通過子類化來創(chuàng)建。 在 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 可以在其它數據結構中使用,而無須強制對象保持存活,但處于存活狀態(tài)的對象也仍然可以通過 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]

終結器對象?

使用 finalize 的主要好處在于它能更簡便地注冊回調函數,而無須保留所返回的終結器對象。 例如

>>>
>>> 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!

終結器也可以被直接調用。 但是終結器最多只能對回調函數發(fā)起一次調用。

>>>
>>> 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() 方法來注銷一個終結器。 該方法將銷毀終結器并返回其被創(chuàng)建時傳給構造器的參數。

>>>
>>> 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,否則終結器在程序退出時如果仍然存活就將被調用。 例如

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

比較終結器與 __del__() 方法?

假設我們想創(chuàng)建一個類,用它的實例來代表臨時目錄。 當以下事件中的某一個發(fā)生時,這個目錄應當與其內容一起被刪除:

  • 對象被作為垃圾回收,

  • 對象的 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__() 方法不會再阻止循環(huán)引用被作為垃圾回收,并且模塊全局變量在 interpreter shutdown 期間不會被強制設為 None。 因此這段代碼在 CPython 上應該會正常運行而不會出現任何問題。

然而,__del__() 方法的處理會嚴重地受到具體實現的影響,因為它依賴于解釋器垃圾回收實現方式的內部細節(jié)。

更健壯的替代方式可以是定義一個終結器,只引用它所需要的特定函數和對象,而不是獲取對整個對象狀態(tài)的訪問權:

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

像這樣定義后,我們的終結器將只接受一個對其完成正確清理目錄任務所需細節(jié)的引用。 如果對象一直未被作為垃圾回收,終結器仍會在退出時被調用。

基于弱引用的終結器還具有另一項優(yōu)勢,就是它們可被用來為定義由第三方控制的類注冊終結器,例如當一個模塊被卸載時運行特定代碼:

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

備注

如果當程序退出時你恰好在守護線程中創(chuàng)建終結器對象,則有可能該終結器不會在退出時被調用。 但是,在一個守護線程中 atexit.register(), try: ... finally: ...with: ... 同樣不能保證執(zhí)行清理。