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

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

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

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

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

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

__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

如果 finalizer 处于活动状态则为 True 的属性,否则为 False。

atexit

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

注意

务必确保 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]

Finalizer 对象

使用 finalize 的主要好处是,它可以轻松注册回调,而无需保留返回的 finalizer 对象。例如

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

也可以直接调用 finalizer。但是 finalizer 最多会调用一次回调。

>>> 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() 方法来注销 finalizer。这会杀死 finalizer,并返回创建时传递给构造函数的参数。

>>> 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,否则如果 finalizer 仍然处于活动状态,则会在程序退出时调用它。例如

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

比较 finalizer 与 __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__() 方法的处理是众所周知的特定于实现的,因为它取决于解释器垃圾回收器实现的内部细节。

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

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

像这样定义,我们的 finalizer 仅接收对其适当清理目录所需详细信息的引用。如果对象永远不会被垃圾回收,则 finalizer 仍将在退出时被调用。

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

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

注意

如果您在守护线程中创建 finalizer 对象,而程序即将退出,则 finalizer 可能不会在退出时被调用。但是,在守护线程中,atexit.register()try: ... finally: ...with: ... 也不能保证清理的发生。