weakref --- 弱引用?

源碼: Lib/weakref.py


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

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

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

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

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

WeakKeyDictionaryWeakValueDictionary 在它們的實現(xiàn)中使用弱引用,在弱引用上設(shè)置回調(diào)函數(shù),當(dāng)鍵或值被垃圾回收回收時通知弱字典。 WeakSet 實現(xiàn)了 set 接口,但像 WeakKeyDictionary 一樣,只持有其元素的弱引用。

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

這些弱容器類型之一或者 finalize 就是大多數(shù)程序所需要的 - 通常不需要直接創(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和代碼對象的支持。

幾個內(nèi)建類型如 listdict 不直接支持弱引用,但可以通過子類化添加支持:

class Dict(dict):
    pass

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

CPython implementation detail: 其他內(nèi)置類型例如 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])?

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

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

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

如果 object 可哈希,則弱引用也為 hashable。 即使在 object 被刪除之后它們?nèi)詫⒈3制涔V怠?如果 hash()object 被刪除之后才首次被調(diào)用,則該調(diào)用將引發(fā) TypeError

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

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

__callback__?

這個只讀屬性會返回當(dāng)前關(guān)聯(lián)到弱引用的回調(diào)。 如果回調(diào)不存在或弱引用的被引用對象已不存在,則此屬性的值為 None。

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

weakref.proxy(object[, callback])?

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

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

weakref.getweakrefcount(object)?

返回指向 object 的弱引用和代理的數(shù)量。

weakref.getweakrefs(object)?

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

class weakref.WeakKeyDictionary([dict])?

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

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

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

WeakKeyDictionary.keyrefs()?

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

class weakref.WeakValueDictionary([dict])?

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

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

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

WeakValueDictionary.valuerefs()?

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

class weakref.WeakSet([elements])?

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

class weakref.WeakMethod(method)?

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

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

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

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

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

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

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

__call__()?

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

detach()?

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

peek()?

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

alive?

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

atexit?

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

備注

很重要的一點是確保 func, argskwargs 不擁有任何對 obj 的引用,無論是直接的或是間接的,否則的話 obj 將永遠不會被作為垃圾回收。 特別地,func 不應(yīng)當(dāng)是 obj 的一個綁定方法。

3.4 新版功能.

weakref.ReferenceType?

弱引用對象的類型對象。

weakref.ProxyType?

不可調(diào)用對象的代理的類型對象。

weakref.CallableProxyType?

可調(diào)用對象的代理的類型對象。

weakref.ProxyTypes?

包含所有代理的類型對象的序列。 這可以用于更方便地檢測一個對象是否是代理,而不必依賴于兩種代理對象的名稱。

參見

PEP 205 - 弱引用

此特性的提議和理由,包括早期實現(xiàn)的鏈接和其他語言中類似特性的相關(guān)信息。

弱引用對象?

弱引用對象沒有 ref.__callback__ 以外的方法和屬性。 一個弱引用對象如果存在,就允許通過調(diào)用它來獲取引用:

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

如果引用已不存在,則調(diào)用引用對象將返回 None:

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

檢測一個弱引用對象是否仍然存在應(yīng)該使用表達式 ref() is not None。 通常,需要使用引用對象的應(yīng)用代碼應(yīng)當(dāng)遵循這樣的模式:

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

使用單獨的“存活”測試會在多線程應(yīng)用中制造競爭條件;其他線程可能導(dǎo)致某個弱引用在該弱引用被調(diào)用前就失效;上述的寫法在多線程應(yīng)用和單線程應(yīng)用中都是安全的。

特別版本的 ref 對象可以通過子類化來創(chuàng)建。 在 WeakValueDictionary 的實現(xiàn)中就使用了這種方式來減少映射中每個條目的內(nèi)存開銷。 這對于將附加信息關(guān)聯(lián)到引用的情況最為適用,但也可以被用于在調(diào)用中插入額外處理來提取引用。

這個例子演示了如何將 ref 的一個子類用于存儲有關(guān)對象的附加信息并在引用被訪問時影響其所返回的值:

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

示例?

這個簡單的例子演示了一個應(yīng)用如何使用對象 ID 來提取之前出現(xiàn)過的對象。 然后對象的 ID 可以在其它數(shù)據(jù)結(jié)構(gòu)中使用,而無須強制對象保持存活,但處于存活狀態(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]

終結(jié)器對象?

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

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

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

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

>>>
>>> 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 屬性設(shè)為 False,否則終結(jié)器在程序退出時如果仍然存活就將被調(diào)用。 例如

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

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

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

  • 對象被作為垃圾回收,

  • 對象的 remove() 方法被調(diào)用,或

  • 程序退出。

我們可以嘗試使用 __del__() 方法來實現(xiàn)這個類,如下所示:

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 期間不會被強制設(shè)為 None。 因此這段代碼在 CPython 上應(yīng)該會正常運行而不會出現(xiàn)任何問題。

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

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

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é)器將只接受一個對其完成正確清理目錄任務(wù)所需細節(jié)的引用。 如果對象一直未被作為垃圾回收,終結(jié)器仍會在退出時被調(diào)用。

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

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

備注

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