weakref
— 弱引用¶
源代码: Lib/weakref.py
weakref
模块允许 Python 程序员创建对象的 弱引用。
在下文中,术语 referent 指的是被弱引用引用的对象。
对一个对象的弱引用不足以使该对象存活:当一个 referent 的唯一剩余引用是弱引用时,垃圾回收 可以自由地销毁该 referent 并将其内存用于其他用途。然而,在对象实际被销毁之前,即使没有强引用指向它,弱引用也可能返回该对象。
弱引用的主要用途是实现缓存或映射,用于存放大型对象,其中希望大型对象不会仅仅因为出现在缓存或映射中而存活。
例如,如果你有许多大型二进制图像对象,你可能希望为每个对象关联一个名称。如果你使用 Python 字典将名称映射到图像,或将图像映射到名称,那么图像对象将仅仅因为它们作为值或键出现在字典中而保持存活。weakref
模块提供的 WeakKeyDictionary
和 WeakValueDictionary
类是替代方案,它们使用弱引用来构造映射,而不会仅仅因为对象出现在映射对象中就使其存活。例如,如果一个图像对象是 WeakValueDictionary
中的一个值,那么当对该图像对象的最后剩余引用是弱映射持有的弱引用时,垃圾回收可以回收该对象,并且它在弱映射中的相应条目也会被删除。
WeakKeyDictionary
和 WeakValueDictionary
在其实现中使用弱引用,为弱引用设置回调函数,当键或值被垃圾回收时通知弱字典。WeakSet
实现了 set
接口,但对其元素保持弱引用,就像 WeakKeyDictionary
所做的那样。
finalize
提供了一种直接的方法来注册一个清理函数,该函数将在对象被垃圾回收时调用。这比在原始弱引用上设置回调函数更简单,因为模块会自动确保 finalizer 在对象被回收之前保持存活。
大多数程序会发现使用这些弱容器类型之一或 finalize
就足够了——通常没有必要直接创建自己的弱引用。weakref
模块公开了底层机制,以满足高级用途的需求。
并非所有对象都可以被弱引用。支持弱引用的对象包括类实例、用 Python 编写的函数(而非 C)、实例方法、集合、不可变集合、一些文件对象、生成器、类型对象、套接字、数组、双端队列、正则表达式模式对象和代码对象。
版本 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
扩展类型可以很容易地支持弱引用;请参阅弱引用支持。
当为给定类型定义了 __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 的代理,该代理使用弱引用。这支持在大多数上下文中使用代理,而无需像弱引用对象那样进行显式解引用。返回对象的类型将是
ProxyType
或CallableProxyType
,具体取决于 object 是否可调用。无论 referent 如何,代理对象都不可 哈希;这避免了许多与其根本可变性相关的深层问题,并阻止它们用作字典键。callback 与ref()
函数同名的参数相同。在 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() >>>
callback 与
ref()
函数同名的参数相同。在 3.4 版本加入。
- class weakref.finalize(obj, func, /, *args, **kwargs)¶
返回一个可调用的终结器对象,当 obj 被垃圾回收时,它将被调用。与普通弱引用不同,终结器将始终存活直到引用对象被回收,这极大地简化了生命周期管理。
终结器在被调用(显式调用或垃圾回收时调用)之前被认为是 活着的,之后则是 死的。调用一个活着的终结器会返回
func(*arg, **kwargs)
的评估结果,而调用一个已死的终结器则返回None
。在垃圾回收过程中,终结器回调引发的异常将显示在标准错误输出中,但无法传播。它们的处理方式与对象
__del__()
方法或弱引用回调引发的异常相同。当程序退出时,除非其
atexit
属性已设置为 false,否则每个剩余的活动终结器都会被调用。它们以创建的相反顺序调用。在解释器关闭的后期,当模块全局变量可能已被
None
替换时,终结器绝不会调用其回调。- alive¶
属性,如果终结器处于活动状态则为 True,否则为 False。
备注
重要的是要确保 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
这样定义后,我们的终结器只接收它清理目录所需的详细信息。如果对象从未被垃圾回收,终结器仍会在退出时被调用。
基于弱引用的终结器的另一个优点是它们可以用于为第三方控制定义的类注册终结器,例如在模块卸载时运行代码:
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: ...
也不能保证清理会发生。