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

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

对对象的弱引用不足以使对象保持活动状态:当对引用对象的唯一剩余引用是弱引用时,垃圾收集可以自由地销毁引用对象并将其内存重新用于其他用途。但是,在对象实际被销毁之前,弱引用可能会返回该对象,即使没有对它的强引用也是如此。

弱引用的主要用途是实现包含大对象的缓存或映射,其中不希望大对象仅仅因为出现在缓存或映射中而保持活动状态。

例如,如果您有许多大型二进制图像对象,您可能希望为每个对象关联一个名称。如果您使用 Python 字典将名称映射到图像,或将图像映射到名称,则图像对象将保持活动状态,因为它们在字典中显示为值或键。 weakref 模块提供的 WeakKeyDictionary 和 WeakValueDictionary 类是另一种选择,它们使用弱引用来构造映射,这些映射不会仅仅因为它们出现在映射对象中而使对象保持活动状态。例如,如果一个图像对象是 WeakValueDictionary 中的一个值,那么当对该图像对象的最后剩余引用是弱映射持有的弱引用时,垃圾回收可以回收该对象,并且它在弱映射中的对应条目只是删除。

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

finalize 提供了一种直接的方法来注册一个清理函数,以便在对象被垃圾回收时调用。这比在原始弱引用上设置回调函数更容易使用,因为模块会自动确保终结器保持活动状态,直到对象被收集。

大多数程序应该发现使用其中一种弱容器类型或 finalize 就是它们所需要的——通常不需要直接创建自己的弱引用。 weakref 模块暴露了低级机制,以便于高级用途。

并不是所有的对象都可以被弱引用;这些对象可以包括类实例、用 Python(但不是用 C)编写的函数、实例方法、集合、frozensets、一些文件对象、生成器、类型对象、套接字、数组、双端队列、正则表达式模式对象和代码对象。

在 3.2 版更改:添加了对 thread.lock、threading.Lock 和代码对象的支持。

list 和 dict 等几种内置类型不直接支持弱引用,但可以通过子类化来添加支持:

class Dict(dict):
    pass

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

CPython 实现细节:其他内置类型(例如 tuple 和 int)即使在子类化时也不支持弱引用

可以很容易地使扩展类型支持弱引用; 请参见弱引用支持。

class weakref.ref(object[callback])
返回对象的弱引用。如果引用对象仍然存在,则可以通过调用引用对象来检索原始对象;如果引用对象不再存在,调用引用对象将导致返回 None。如果提供了回调而不是 None,并且返回的 weakref 对象仍然存在,则将在对象即将完成时调用回调;弱引用对象将作为唯一参数传递给回调;引用对象将不再可用。

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

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

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

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

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

__打回来__
此只读属性返回当前关联到 weakref 的回调。如果没有回调或者 weakref 的引用不再存在,那么这个属性的值将为 None。

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

weakref.proxy(object[callback])
将代理返回给使用弱引用的对象。 这支持在大多数情况下使用代理,而不是要求对弱引用对象使用显式取消引用。 返回的对象将具有 ProxyType 或 CallableProxyType 类型,具体取决于对象是否可调用。 无论引用对象如何,代理对象都不可散列; 这避免了一些与其基本可变性质相关的问题,并防止将它们用作字典键。 callback 与 ref() 函数的同名参数相同。
weakref.getweakrefcount(object)
返回引用对象的弱引用和代理的数量。
weakref.getweakrefs(object)
返回引用对象的所有弱引用和代理对象的列表。
class weakref.WeakKeyDictionary([dict])
弱引用键的映射类。 当不再有对键的强引用时,字典中的条目将被丢弃。 这可用于将附加数据与应用程序其他部分拥有的对象相关联,而无需向这些对象添加属性。 这对于覆盖属性访问的对象特别有用。

WeakKeyDictionary 对象有一个直接公开内部引用的附加方法。 不能保证引用在使用时是“活的”,因此调用引用的结果需要在使用前进行检查。 这可用于避免创建引用,这将导致垃圾收集器将密钥保留的时间超过需要的时间。

WeakKeyDictionary.keyrefs()
返回对键的弱引用的可迭代。
class weakref.WeakValueDictionary([dict])
弱引用值的映射类。 当不再存在对该值的强引用时,字典中的条目将被丢弃。

WeakValueDictionary对象具有与对象方法具有相同问题的其他keyrefs()方法WeakKeyDictionary 。

WeakValueDictionary.valuerefs()
返回对值的弱引用的可迭代。
class weakref.WeakSet([elements])
设置对其元素保持弱引用的类。 当不再存在对其的强引用时,该元素将被丢弃。
class weakref.WeakMethod(method)
自定义 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()
>>>

版本3.4中的新功能。

class weakref.finalize(objfunc*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 为真。 它们以相反的创建顺序被调用。

注意

重要的是要确保 func、args 和 kwargs 不直接或间接拥有对 obj 的任何引用,否则 obj 将永远不会被垃圾收集。 特别是,func 不应该是 obj 的绑定方法。

版本3.4中的新功能。

weakref.ReferenceType
弱引用对象的类型对象。
weakref.ProxyType
不可调用的对象代理的类型对象。
weakref.CallableProxyType
可调用对象代理的类型对象。
weakref.ProxyTypes
包含代理的所有类型对象的序列。这可以使测试对象是否是代理更简单,而不依赖于命名两种代理类型。

弱引用对象

弱引用对象除了 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(ExtendedRef, self).__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(ExtendedRef, self).__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!")  #doctest:+ELLIPSIS
<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()                                           #doctest:+ELLIPSIS
(<...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")  #doctest:+ELLIPSIS
<finalize object at ...; for 'Object' at ...>
>>> exit()                                               #doctest:+SKIP
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: … 也不保证会发生清理。