weakref --- 弱引用?

源碼: Lib/weakref.py


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

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

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

弱引用的主要用途是實(shí)現(xiàn)保存大對(duì)象的高速緩存或映射,但又不希望大對(duì)象僅僅因?yàn)樗霈F(xiàn)在高速緩存或映射中而保持存活。

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

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

finalize 提供了注冊(cè)一個(gè)對(duì)象被垃圾收集時(shí)要調(diào)用的清理函數(shù)的方式。這比在原始弱引用上設(shè)置回調(diào)函數(shù)更簡(jiǎn)單,因?yàn)槟K會(huì)自動(dòng)確保對(duì)象被回收前終結(jié)器一直保持存活。

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

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 版更改: 添加了對(duì)thread.lock,threading.Lock和代碼對(duì)象的支持。

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

class Dict(dict):
    pass

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

CPython implementation detail: 其他內(nèi)置類型例如 tupleint 不支持弱引用,即使通過(guò)子類化也不支持。

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

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

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

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

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

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

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

__callback__?

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

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

weakref.proxy(object[, callback])?

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

在 3.8 版更改: 擴(kuò)展代理對(duì)象所支持的運(yùn)算符,包括矩陣乘法運(yùn)算符 @@=

weakref.getweakrefcount(object)?

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

weakref.getweakrefs(object)?

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

class weakref.WeakKeyDictionary([dict])?

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

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

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

WeakKeyDictionary.keyrefs()?

返回包含對(duì)鍵的弱引用的可迭代對(duì)象。

class weakref.WeakValueDictionary([dict])?

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

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

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

WeakValueDictionary.valuerefs()?

返回包含對(duì)值的弱引用的可迭代對(duì)象。

class weakref.WeakSet([elements])?

保持對(duì)其元素弱引用的集合類。 當(dāng)不再有對(duì)某個(gè)元素的強(qiáng)引用時(shí)元素將被丟棄。

class weakref.WeakMethod(method)?

一個(gè)模擬對(duì)綁定方法(即在類中定義并在實(shí)例中查找的方法)進(jìn)行弱引用的自定義 ref 子類。 由于綁定方法是臨時(shí)性的,標(biāo)準(zhǔn)弱引用無(wú)法保持它。 WeakMethod 包含特別代碼用來(lái)重新創(chuàng)建綁定方法,直到對(duì)象或初始函數(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)?

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

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

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

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

終結(jié)器在 interpreter shutdown 的后期絕不會(huì)發(fā)起調(diào)用其回調(diào)函數(shù),此時(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?

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

備注

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

3.4 新版功能.

weakref.ReferenceType?

弱引用對(duì)象的類型對(duì)象。

weakref.ProxyType?

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

weakref.CallableProxyType?

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

weakref.ProxyTypes?

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

參見(jiàn)

PEP 205 - 弱引用

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

弱引用對(duì)象?

弱引用對(duì)象沒(méi)有 ref.__callback__ 以外的方法和屬性。 一個(gè)弱引用對(duì)象如果存在,就允許通過(guò)調(diào)用它來(lái)獲取引用:

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

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

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

檢測(cè)一個(gè)弱引用對(duì)象是否仍然存在應(yīng)該使用表達(dá)式 ref() is not None。 通常,需要使用引用對(duì)象的應(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()

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

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

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

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

示例?

這個(gè)簡(jiǎn)單的例子演示了一個(gè)應(yīng)用如何使用對(duì)象 ID 來(lái)提取之前出現(xiàn)過(guò)的對(duì)象。 然后對(duì)象的 ID 可以在其它數(shù)據(jù)結(jié)構(gòu)中使用,而無(wú)須強(qiáng)制對(duì)象保持存活,但處于存活狀態(tài)的對(duì)象也仍然可以通過(guò) ID 來(lái)提取。

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é)器對(duì)象?

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

>>>
>>> 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é)器最多只能對(duì)回調(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() 方法來(lái)注銷一個(gè)終結(jié)器。 該方法將銷毀終結(jié)器并返回其被創(chuàng)建時(shí)傳給構(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é)器在程序退出時(shí)如果仍然存活就將被調(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)建一個(gè)類,用它的實(shí)例來(lái)代表臨時(shí)目錄。 當(dāng)以下事件中的某一個(gè)發(fā)生時(shí),這個(gè)目錄應(yīng)當(dāng)與其內(nèi)容一起被刪除:

  • 對(duì)象被作為垃圾回收,

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

  • 程序退出。

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

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 開(kāi)始,__del__() 方法不會(huì)再阻止循環(huán)引用被作為垃圾回收,并且模塊全局變量在 interpreter shutdown 期間不會(huì)被強(qiáng)制設(shè)為 None。 因此這段代碼在 CPython 上應(yīng)該會(huì)正常運(yùn)行而不會(huì)出現(xiàn)任何問(wèn)題。

然而,__del__() 方法的處理會(huì)嚴(yán)重地受到具體實(shí)現(xiàn)的影響,因?yàn)樗蕾囉诮忉屍骼厥諏?shí)現(xiàn)方式的內(nèi)部細(xì)節(jié)。

更健壯的替代方式可以是定義一個(gè)終結(jié)器,只引用它所需要的特定函數(shù)和對(duì)象,而不是獲取對(duì)整個(gè)對(duì)象狀態(tài)的訪問(wèn)權(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é)器將只接受一個(gè)對(duì)其完成正確清理目錄任務(wù)所需細(xì)節(jié)的引用。 如果對(duì)象一直未被作為垃圾回收,終結(jié)器仍會(huì)在退出時(shí)被調(diào)用。

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

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)程序退出時(shí)你恰好在守護(hù)線程中創(chuàng)建終結(jié)器對(duì)象,則有可能該終結(jié)器不會(huì)在退出時(shí)被調(diào)用。 但是,在一個(gè)守護(hù)線程中 atexit.register(), try: ... finally: ...with: ... 同樣不能保證執(zhí)行清理。