Skip to main content

8.8. weakref —弱参考

源代码: Lib/weakref.py


weakref 模块允许Python程序员为对象创建 weak references

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

对对象的弱引用不足以保持对象存活:当对引用对象的仅剩余引用是弱引用时,garbage collection 可以自由地销毁引用对象并将其内存重用于其他对象。然而,直到对象被实际销毁,弱引用可以返回对象,即使没有对它的强引用。

弱引用的主要用途是实现持有大对象的高速缓存或映射,其中希望大对象不会因为它出现在高速缓存或映射中而被保持活着。

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

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

finalize 提供了一种直接的方式来注册当对象被垃圾收集时要调用的清理函数。这比在原始弱引用上设置回调函数更简单,因为模块自动确保终结器保持活动直到对象被收集。

大多数程序应该发现使用这些弱容器类型或 finalize 中的一个是他们需要的 - 通常不需要直接创建自己的弱引用。低级机械由 weakref 模块暴露,用于高级用途的利益。

不是所有的对象都可以弱引用;这些对象可以包括类实例,用Python(但不是C)编写的函数,实例方法,集合,frozensets,一些 文件对象generator s,类型对象,套接字,数组,deques,正则表达式模式对象和代码对象。

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

一些内置类型,如 listdict 不直接支持弱引用,但可以通过子类添加支持:

class Dict(dict):
    pass

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

其他内置类型,如 tupleint 不支持弱引用,即使子类化(这是一个实现细节,可能在不同的Python实现中是不同的。

扩展类型可以轻松地支持弱引用;见 弱参考支持

class weakref.ref(object[, callback])

返回一个弱引用 object。如果引用对象仍然存在,则可以通过调用引用对象来检索原始对象;如果引用对象不再存活,调用引用对象将导致返回 None。如果提供了 callback 而不是 None,并且返回的weakref对象仍然有效,则当对象即将完成时将调用回调;弱引用对象将作为唯一的参数传递给回调函数;指示物将不再可用。

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

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

弱引用是 hashable,如果 object 是哈希的。即使在删除 object 之后,它们仍将保持其散列值。如果在 object 被删除之后第一次调用 hash(),则呼叫将产生 TypeError

弱参考支持相等测试,但不支持排序。如果指示物仍然存在,则两个引用具有与它们的指示物相同的等同关系(不管 callback)。如果任一指示对象已被删除,则只有当引用对象是同一对象时,引用才相等。

这是一个子类型而不是工厂函数。

__callback__

此只读属性返回当前与weakref关联的回调。如果没有回调,或者如果weakref的指示不再存活,则该属性将具有值 None

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

weakref.proxy(object[, callback])

将代理返回到使用弱引用的 object。这支持在大多数上下文中使用代理,而不需要使用弱引用对象的显式取消引用。返回的对象将具有 ProxyTypeCallableProxyType 的类型,这取决于 object 是否可调用。代理对象不是 hashable,而不管指示对象;这避免了与其基本上可变的性质相关的许多问题,并且防止它们用作字典键。 callbackref() 功能的相同名称的参数相同。

weakref.getweakrefcount(object)

返回引用 object 的弱引用和代理的数量。

weakref.getweakrefs(object)

返回引用 object 的所有弱引用和代理对象的列表。

class weakref.WeakKeyDictionary([dict])

弱引用键的映射类。当不再有对键的强引用时,字典中的条目将被丢弃。这可以用于将附加数据与应用程序的其他部分拥有的对象相关联,而不向这些对象添加属性。这对于覆盖属性访问的对象尤其有用。

注解

注意:因为 WeakKeyDictionary 构建在Python字典之上,所以在迭代它时不能改变大小。这可能难以确保 WeakKeyDictionary,因为在迭代期间由程序执行的动作可能导致词典中的项目“由魔术”消失(作为垃圾收集的副作用)。

WeakKeyDictionary 对象具有以下附加方法。这些直接暴露内部引用。引用不能保证在使用时是“活的”,因此调用引用的结果需要在使用之前检查。这可以用于避免创建引用,这将导致垃圾收集器保持密钥长于所需的时间。

WeakKeyDictionary.keyrefs()

返回键的弱引用的可迭代。

class weakref.WeakValueDictionary([dict])

弱引用值的映射类。当没有对值的强引用存在时,字典中的条目将被丢弃。

注解

注意:因为 WeakValueDictionary 构建在Python字典之上,所以在迭代它时不能改变大小。这可能难以确保 WeakValueDictionary,因为在迭代期间由程序执行的动作可能导致词典中的项目“由魔术”消失(作为垃圾收集的副作用)。

WeakValueDictionary 对象具有以下附加方法。这些方法具有与 WeakKeyDictionary 对象的 keyrefs() 方法相同的问题。

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(obj, func, *args, **kwargs)

返回一个可调用的终结器对象,当 obj 被垃圾收集时将被调用。与普通弱引用不同,终结器将始终存活,直到收集引用对象,从而大大简化了生命周期管理。

终结器被认为是 alive,直到它被调用(显式地或在垃圾收集),之后它是 dead。调用实时终结器返回评估 func(*arg, **kwargs) 的结果,而调用死终结器返回 None

垃圾回收期间由终结器回调引发的异常将显示在标准错误输出上,但不能传播。它们以与从对象的 __del__() 方法或弱引用的回调引发的异常相同的方式处理。

当程序退出时,除非其 atexit 属性已设置为false,否则将调用每个剩余的活动终结器。他们被称为创造的相反顺序。

当模块全局变量很可能被 None 替代时,终结器将永远不会在 interpreter shutdown 的后面部分调用它的回调。

__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 为真的所有剩余的活终结器。他们被称为创造的相反顺序。

注解

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

3.4 新版功能.

weakref.ReferenceType

弱引用对象的类型对象。

weakref.ProxyType

不可调用的对象的代理的类型对象。

weakref.CallableProxyType

可调用对象的代理的类型对象。

weakref.ProxyTypes

包含代理的所有类型对象的序列。这可以使测试对象是否是代理而不依赖于命名这两种代理类型更简单。

exception weakref.ReferenceError

使用代理对象但已收集基础对象时引发的异常。这与标准 ReferenceError 异常相同。

参见

PEP 205 - 弱参考

此功能的提议和基本原理,包括指向早期实现的链接以及其他语言中类似功能的信息。

8.8.1. 弱参考对象

弱参考对象除了 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

8.8.2. 例

这个简单的例子显示了应用程序如何使用对象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]

8.8.3. 终结器对象

使用 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()                                           
(<__main__.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

8.8.4. 比较终结剂与 __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__() 方法不再阻止引用循环被垃圾回收,并且模块全局变量在 interpreter shutdown 期间不再被强制为 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

像这样定义,我们的终结器只接收对它需要适当地清理目录的细节的引用。如果对象永远不会被垃圾收集,终结器仍将在退出时被调用。

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

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

注解

如果你在程序退出时在daemonic线程中创建一个终结器对象,那么终结器有可能在退出时不被调用。然而,在守护线程 atexit.register() 中,try: ... finally: ...with: ... 不保证也进行清除。