weakref — 弱引用

源代码: Lib/weakref.py


weakref 模块允许 Python 程序员创建对象的 弱引用

在下文中,术语 referent 指的是被弱引用引用的对象。

对一个对象的弱引用不足以使该对象存活:当一个 referent 的唯一剩余引用是弱引用时,垃圾回收 可以自由地销毁该 referent 并将其内存用于其他用途。然而,在对象实际被销毁之前,即使没有强引用指向它,弱引用也可能返回该对象。

弱引用的主要用途是实现缓存或映射,用于存放大型对象,其中希望大型对象不会仅仅因为出现在缓存或映射中而存活。

例如,如果你有许多大型二进制图像对象,你可能希望为每个对象关联一个名称。如果你使用 Python 字典将名称映射到图像,或将图像映射到名称,那么图像对象将仅仅因为它们作为值或键出现在字典中而保持存活。weakref 模块提供的 WeakKeyDictionaryWeakValueDictionary 类是替代方案,它们使用弱引用来构造映射,而不会仅仅因为对象出现在映射对象中就使其存活。例如,如果一个图像对象是 WeakValueDictionary 中的一个值,那么当对该图像对象的最后剩余引用是弱映射持有的弱引用时,垃圾回收可以回收该对象,并且它在弱映射中的相应条目也会被删除。

WeakKeyDictionaryWeakValueDictionary 在其实现中使用弱引用,为弱引用设置回调函数,当键或值被垃圾回收时通知弱字典。WeakSet 实现了 set 接口,但对其元素保持弱引用,就像 WeakKeyDictionary 所做的那样。

finalize 提供了一种直接的方法来注册一个清理函数,该函数将在对象被垃圾回收时调用。这比在原始弱引用上设置回调函数更简单,因为模块会自动确保 finalizer 在对象被回收之前保持存活。

大多数程序会发现使用这些弱容器类型之一或 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 的弱引用。如果 referent 仍然存活,可以通过调用引用对象来检索原始对象;如果 referent 不再存活,调用引用对象将导致返回 None。如果提供了 callback 且不为 None,并且返回的弱引用对象仍然存活,则当对象即将被终结时,将调用 callback;弱引用对象将作为唯一参数传递给 callback;此时 referent 将不再可用。

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

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

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

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

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

__callback__

此只读属性返回当前与弱引用关联的回调。如果没有回调,或者弱引用的 referent 不再存活,则此属性的值将为 None

版本 3.4 中有修改: 添加了 __callback__ 属性。

weakref.proxy(object[, callback])

返回一个对 object 的代理,该代理使用弱引用。这支持在大多数上下文中使用代理,而无需像弱引用对象那样进行显式解引用。返回对象的类型将是 ProxyTypeCallableProxyType,具体取决于 object 是否可调用。无论 referent 如何,代理对象都不可 哈希;这避免了许多与其根本可变性相关的深层问题,并阻止它们用作字典键。callbackref() 函数同名的参数相同。

在 referent 被垃圾回收后访问代理对象的属性会引发 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()
>>>

callbackref() 函数同名的参数相同。

在 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。它们以创建的相反顺序调用。

备注

重要的是要确保 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]

终结器对象

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

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

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

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: ... 也不能保证清理会发生。