8.8. weakref — 弱引用 — Python 文档
8.8. 弱引用 — 弱引用
weakref 模块允许 Python 程序员创建对对象的 弱引用 。
在下文中,术语 referent 表示被弱引用引用的对象。
对对象的弱引用不足以使对象保持活动状态:当对所指对象的唯一剩余引用是弱引用时,垃圾收集 可以自由地销毁所指对象并将其内存重用于其他用途。 然而,在对象被实际销毁之前,即使没有强引用,弱引用也可能返回该对象。
弱引用的主要用途是实现保存大对象的缓存或映射,在这种情况下,希望大对象不会仅仅因为它出现在缓存或映射中而保持活动状态。
例如,如果您有许多大型二进制图像对象,您可能希望为每个对象关联一个名称。 如果您使用 Python 字典将名称映射到图像,或将图像映射到名称,则图像对象将保持活动状态,因为它们在字典中作为值或键出现。 由 weakref 模块提供的 WeakKeyDictionary 和 WeakValueDictionary 类是另一种选择,使用弱引用来构造映射,这些映射不会仅仅因为对象出现在映射对象。 例如,如果图像对象是 WeakValueDictionary 中的一个值,那么当对该图像对象的最后剩余引用是弱映射持有的弱引用时,垃圾收集可以回收该对象及其对应的弱映射中的条目被简单地删除。
WeakKeyDictionary 和 WeakValueDictionary 在它们的实现中使用弱引用,在弱引用上设置回调函数,当一个键或值被垃圾收集回收时通知弱字典。 WeakSet 实现了 set 接口,但保持对其元素的弱引用,就像 WeakKeyDictionary 一样。
finalize 提供了一种直接的方法来注册一个清理函数,当一个对象被垃圾回收时要调用。 这比在原始弱引用上设置回调函数更容易使用,因为模块会自动确保终结器在收集对象之前保持活动状态。
大多数程序应该会发现使用这些弱容器类型之一或 finalize 就是他们所需要的——通常不需要直接创建自己的弱引用。 低级机制由 weakref 模块公开,以便于高级用途。
并非所有对象都可以弱引用; 这些对象可以包括类实例、用 Python 编写的函数(但不是用 C 编写的)、实例方法、集合、冻结集、一些 文件对象 、 生成器 、类型对象、套接字、数组、双端队列、正则表达式模式对象和代码对象。
3.2 版更改: 添加了对 thread.lock、threading.Lock 和 code 对象的支持。
一些内置类型,例如 list 和 dict 不直接支持弱引用,但可以通过子类化添加支持:
class Dict(dict):
pass
obj = Dict(red=1, green=2, blue=3) # this object is weak referenceable
其他内置类型,例如 tuple 和 int 即使在子类化时也不支持弱引用(这是一个实现细节,在各种 Python 实现中可能有所不同。)。
可以很容易地使扩展类型支持弱引用; 请参阅 弱参考支持 。
- class weakref.ref(object[, callback])
返回对 object 的弱引用。 如果引用对象还活着,则可以通过调用引用对象来检索原始对象; 如果引用对象不再存在,调用引用对象将导致返回 None。 如果提供了 callback 而不是 None,并且返回的 weakref 对象还活着,则在对象即将完成时会调用回调; 弱引用对象将作为唯一参数传递给回调; 参照物将不再可用。
允许为同一个对象构造多个弱引用。 为每个弱引用注册的回调将从最近注册的回调调用到最早注册的回调。
回调引发的异常将在标准错误输出中注明,但不能传播; 它们的处理方式与从对象的
__del__()
方法引发的异常完全相同。如果 对象 是可散列的,则弱引用是 可散列 。 即使在 对象 被删除后,它们也会保持其哈希值。 如果 hash() 仅在删除 object 后第一次调用,该调用将引发 TypeError。
弱引用支持相等性测试,但不支持排序。 如果引用对象还活着,则两个引用与它们的引用对象具有相同的相等关系(不管 callback)。 如果已删除任一引用对象,则仅当引用对象是同一对象时,引用才相等。
这是一个可子类的类型,而不是工厂函数。
- __callback__
这个只读属性返回当前关联到弱引用的回调。 如果没有回调或者如果weakref 的所指对象不再存在,则该属性将具有值
None
。
3.4 版更改: 添加 __callback__ 属性。
- weakref.proxy(object[, callback])
- 返回一个使用弱引用的 object 的代理。 这支持在大多数上下文中使用代理,而不需要与弱引用对象一起使用的显式取消引用。 返回的对象将具有
ProxyType
或CallableProxyType
的类型,具体取决于 object 是否可调用。 无论所指对象如何,代理对象都不是 可散列的 ; 这避免了许多与其基本可变性质相关的问题,并防止将它们用作字典键。 callback与ref()函数的同名参数相同。
- 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 替换时,终结器永远不会在 解释器关闭 的后期调用其回调。
- __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
- 包含代理的所有类型对象的序列。 这可以更简单地测试对象是否是代理,而无需依赖于对两种代理类型的命名。
- exception weakref.ReferenceError
- 使用代理对象但已收集基础对象时引发异常。 这与标准的 ReferenceError 异常相同。
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__()
方法不再阻止引用循环被垃圾收集,并且模块全局变量在 解释器关闭 期间不再被强制为 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: ...
也不保证会发生清理。