8.8. weakref
- 弱参考¶
源代码: Lib / weakref.py
weakref
模块允许Python程序员为对象创建弱引用。
在下文中,术语指代意味着弱引用所引用的对象。
对对象的弱引用不足以保持对象存活:当对引用对象的仅剩余引用是弱引用时,garbage collection可以自由地销毁指针,并将其内存重用于其他内容。然而,直到对象被实际销毁,弱引用可以返回对象,即使没有对它的强引用。
弱引用的主要用途是实现持有大对象的高速缓存或映射,其中希望大对象不会因为它出现在高速缓存或映射中而保持活着。
例如,如果您有一些大的二进制图像对象,您可能希望将一个名称与每个对象相关联。如果您使用Python字典将名称映射到图像,或将图像映射到名称,则图像对象将仍然存在,因为它们在字典中显示为值或键。由weakref
模块提供的WeakKeyDictionary
和WeakValueDictionary
类是一种替代方法,使用弱引用来构造不保留对象的映射,它们出现在映射对象中。例如,如果图像对象是WeakValueDictionary
中的值,则当对该图像对象的最后剩余引用是弱映射所持有的弱引用时,垃圾容器可以回收该对象,并且其弱映射中的相应条目被简单地删除。
WeakKeyDictionary
和WeakValueDictionary
在其实现中使用弱引用,在垃圾容器回收键或值时通知弱字典的弱引用上设置回调函数。WeakSet
实现set
接口,但保持对其元素的弱引用,就像WeakKeyDictionary
。
finalize
提供了一种直接的方式来注册要在对象被垃圾回收时调用的清除函数。这比在原始弱引用上设置回调函数更容易使用,因为模块自动确保终结器保持活动直到对象被收集。
大多数程序应该发现使用这些弱容器类型之一或finalize
是他们需要的 - 通常不需要直接创建自己的弱引用。低级机械由weakref
模块暴露,以实现高级用途。
不是所有的对象都可以弱引用;这些对象可以包括类实例,用Python(但不是C)写的函数,实例方法,集,frozensets,一些file objects,generator ,套接字,数组,deques,正则表达式模式对象和代码对象。
在版本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
其他内建类型如tuple
和int
即使在子类化时也不支持弱引用(这是一个实现细节,可能在不同的Python实现中是不同的。)。
扩展类型可以很容易地支持弱引用;请参阅Weak Reference Support。
- class
weakref.
ref
(object[, callback])¶ 返回对对象的弱引用。如果引用对象仍然存在,则可以通过调用引用对象来检索原始对象;如果引用对象不再活动,则调用引用对象将导致返回
None
。如果提供callback而不是None
,并且返回的weakref对象仍然有效,则当对象即将完成时将调用回调;弱引用对象将作为唯一的参数传递给回调函数;指示物将不再可用。可以为同一对象构造许多弱引用。为每个弱引用注册的回调将从最近注册的回调调用到最早注册的回调。
回调引发的异常将在标准错误输出上注明,但不能传播;它们的处理方式与从对象的
__del__()
方法引发的异常完全相同。如果对象是可散列的,则弱引用hashable。即使在删除对象之后,它们仍将保持其散列值。如果
hash()
仅在对象删除后第一次调用,则调用将引发TypeError
。弱引用支持相等测试,但不支持排序。如果引用仍然存在,则两个引用与它们的引用具有相同的等式关系(不管回调)。如果引用对象已被删除,则引用仅在引用对象是同一对象时相等。
这是一个可子类型而不是工厂函数。
__ callback __
¶此只读属性返回当前与weakref关联的回调。如果没有回调,或者如果weakref的引用不再活,那么该属性将具有值
None
。
在版本3.4中已更改:添加了
__callback__
属性。
weakref.
proxy
(object[, callback])¶将代理返回到使用弱引用的对象。这支持在大多数上下文中使用代理,而不需要使用弱引用对象的显式取消引用。根据对象是否可调用,返回的对象将具有
ProxyType
或CallableProxyType
的类型。代理对象不是hashable,无论指定对象;这避免了与它们的基本上可变性质相关的许多问题,并且防止它们用作字典键。回调与ref()
函数同名的参数相同。
weakref.
getweakrefcount
(object)¶返回引用对象的弱引用和代理的数量。
weakref.
getweakrefs
(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被垃圾收集时将被调用。与普通弱引用不同,终结器将始终存活,直到收集引用对象,从而大大简化了生命周期管理。
终结器被认为是存活,直到它被调用(显式地或在垃圾容器),然后是死。调用实时终结器将返回评估
func(* arg, ** kwargs)
的结果,而调用死终结器返回None
。垃圾容器期间由finalizer回调引发的异常将显示在标准错误输出上,但不能传播。它们以与从对象的
__del__()
方法或弱引用的回调引发的异常相同的方式处理。当程序退出时,除非其
atexit
属性已设置为false,否则将调用每个剩余的实时终结器。他们被称为创造的相反顺序。当模块全局变量被
None
替换时,终结器将不会在interpreter shutdown的后面部分调用其回调。alive
¶属性,如果终结器是活的,则为true,否则为false。
注意
重要的是确保func,args和kwargs不拥有对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.Finalizer对象¶
使用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()
Starting with Python 3.4, __del__()
methods no longer prevent reference cycles from being garbage collected, and module globals are no longer forced to None
during interpreter shutdown. 所以这个代码应该工作没有任何问题在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
像这样定义,我们的终结器只接收对它需要的细节的引用,以适当地清理目录。如果对象永远不会被垃圾收集,终结器仍将在退出时被调用。
基于weakref的终结器的另一个优点是它们可以用于为定义由第三方控制的类注册终结器,例如当模块卸载时运行代码:
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: ...
和与: ...