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 对象仍然存在,则在对象即将被终结时将调用该回调;weakref 对象将作为唯一参数传递给回调;引用对象将不再可用。

允许为同一个对象构造多个弱引用。为每个弱引用注册的回调将按照从最近注册的回调到最早注册的回调的顺序调用。

回调引发的异常将记录在标准错误输出中,但不能传播;它们的处理方式与从对象的 __del__() 方法引发的异常完全相同。

如果*对象*是可哈希的,则弱引用是可哈希的。即使在*对象*被删除后,它们也会保留其哈希值。如果在*对象*被删除后第一次调用hash(),则调用将引发TypeError

弱引用支持相等性测试,但不支持排序。如果引用仍然有效,则两个引用与其引用具有相同的相等关系(无论*回调*如何)。如果任一引用已被删除,则仅当引用对象是同一个对象时,引用才相等。

这是一个可子类化的类型,而不是工厂函数。

__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

如果终结器是活动的,则此属性为 true,否则为 false。

atexit

一个可写的布尔属性,默认值为 true。程序退出时,它会调用 atexit 为 true 的所有剩余活动终结器。它们的调用顺序与创建顺序相反。

注意

重要的是要确保 *func*、*args* 和 *kwargs* 不直接或间接拥有对 *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]

终结器对象

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

也可以直接调用终结器。但是,终结器最多只会调用回调一次。

>>> 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() 方法取消注册它。这将终止终结器并返回创建它时传递给构造函数的参数。

>>> 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__() 方法

假设我们要创建一个类,其实例表示临时目录。当发生以下第一个事件时,应删除目录及其内容

  • 对象被垃圾回收,

  • 调用对象的 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__() 方法的处理方式因实现而异,因为它取决于解释器垃圾回收器实现的内部细节。

一种更稳健的替代方法是定义一个终结器,它只引用它需要的特定函数和对象,而不是访问对象的完整状态。

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

像这样定义后,我们的终结器只接收对其需要清理目录的详细信息的引用。如果对象从未被垃圾回收,终结器仍然会在退出时被调用。

基于 weakref 的终结器的另一个优点是,它们可以用于为定义由第三方控制的类注册终结器,例如在模块卸载时运行代码。

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

注意

如果您在守护线程中创建终结器对象,就像程序退出时一样,那么终结器有可能在退出时不会被调用。但是,在守护线程中,atexit.register()try: ... finally: ...with: ... 也不能保证清理会发生。