weakref-弱引用详解(37)Python语言(必读进阶学习教程)(参考资料)
该weakref
模块允许Python程序员创建对对象的弱引用。
在下文中,术语“ 指示对象”表示由弱引用引用的对象。
对对象的弱引用不足以使对象保持活动状态:当对引用的唯一剩余引用是弱引用时, 垃圾收集可以自由地销毁引用并将其内存重用于其他内容。但是,在实际销毁对象之前,弱引用可能会返回对象,即使没有对它的强引用也是如此。
弱引用的主要用途是实现保存大对象的高速缓存或映射,其中希望大对象不能仅仅因为它出现在高速缓存或映射中而保持活动。
例如,如果您有许多大型二进制图像对象,则可能希望将名称与每个对象关联。如果您使用Python字典将名称映射到图像,或将图像映射到名称,则图像对象将保持活动状态,因为它们在字典中显示为值或键。模块提供的WeakKeyDictionary
和WeakValueDictionary
类weakref
是一种替代方法,使用弱引用来构造映射,这些映射不会仅仅因为它们出现在映射对象中而使对象保持活动状态。例如,如果图像对象是a中的值 WeakValueDictionary
,那么当对该图像对象的最后剩余引用是弱映射所保持的弱引用时,垃圾收集可以回收该对象,并且仅删除其在弱映射中的相应条目。
WeakKeyDictionary
并WeakValueDictionary
在其实现中使用弱引用,在弱引用上设置回调函数,当垃圾收集回收键或值时通知弱字典。 WeakSet
实现set
接口,但保持对其元素的弱引用,就像一个 WeakKeyDictionary
。
finalize
提供了一种直接的方式来注册在对象被垃圾收集时要调用的清理函数。这比在原始弱引用上设置回调函数更简单,因为模块会自动确保终结器保持活动状态,直到收集对象为止。
大多数程序应该发现使用这些弱容器类型之一或者finalize
只需要它们 – 通常不需要直接创建自己的弱引用。weakref
模块暴露出低级机械,以利于高级用途。
并非所有对象都可以被弱引用; 那些可以包含类实例的对象,用Python编写的函数(但不包括在C中),实例方法,集合,frozensets,一些文件对象,生成器,类型对象,套接字,数组,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实现不同。)不支持弱引用。
可以轻松地使用扩展类型来支持弱引用; 请参阅 弱参考支持。
- class
weakref.
ref
(object [,callback ] ) - 返回对象的弱引用。如果指示对象仍然存在,则可以通过调用引用对象来检索原始对象; 如果指示对象不再存在,则调用引用对象将导致
None
返回。如果提供了回调而没有None
,并且返回的weakref对象仍然存活,则在对象即将完成时将调用回调; 弱引用对象将作为唯一参数传递给回调; 指示物将不再可用。允许为同一对象构造许多弱引用。为每个弱引用注册的回调将从最近注册的回调调用到最早注册的回调。
回调引发的异常将在标准错误输出上注明,但不能传播; 它们的处理方式与从对象
__del__()
方法引发的异常完全相同。如果对象是可清除的,则弱引用是可清除的。即使在删除对象后,它们也将保持其哈希值。如果 仅在删除对象后第一次调用,则调用将引发。
hash()
TypeError
弱引用支持测试相等,但不支持排序。如果所指对象仍然存在,则两个引用与它们的引用具有相同的相等关系(无论回调如何)。如果已删除任一引用对象,则仅当引用对象是同一对象时引用才相等。
这是一个可子类型而不是工厂函数。
__callback__
- 此只读属性返回当前与weakref关联的回调。如果没有回调或者weakref的指示对象不再存在,那么该属性将具有值
None
。
版本3.4中已更改:添加了
__callback__
属性。
weakref.
proxy
(对象[,回调] )- 将代理返回给使用弱引用的对象。这支持在大多数上下文中使用代理,而不需要使用与弱引用对象一起使用的显式解除引用。返回的对象将具有
ProxyType
或者类型CallableProxyType
,具体取决于对象是否可调用。无论指示对象,代理对象都不可清除 ; 这避免了与其基本可变性相关的许多问题,并阻止它们用作字典键。 回调与ref()
函数同名的参数相同。
weakref.
getweakrefcount
(对象)- 返回引用object的弱引用和代理的数量。
weakref.
getweakrefs
(对象)- 回到这指的是所有弱引用和代理对象的列表对象。
- class
weakref.
WeakKeyDictionary
([ dict ] ) - 映射弱引用键的类。当不再有对密钥的强引用时,将丢弃字典中的条目。这可用于将其他数据与应用程序的其他部分所拥有的对象相关联,而无需向这些对象添加属性。这对于覆盖属性访问的对象尤其有用。
注意
警告:因为a
WeakKeyDictionary
是基于Python字典构建的,所以在迭代它时不能改变大小。这可能难以确保,WeakKeyDictionary
因为程序在迭代期间执行的操作可能导致字典中的项目“通过魔法”消失(作为垃圾收集的副作用)。
WeakKeyDictionary
对象有一个直接公开内部引用的附加方法。引用不保证在使用时是“活动的”,因此在使用之前需要检查调用引用的结果。这可以用于避免创建引用,这将导致垃圾收集器将密钥保持在比所需更长的时间。
WeakKeyDictionary.
keyrefs
()- 返回对键的弱引用的可迭代。
- class
weakref.
WeakValueDictionary
([ dict ] ) - 映射弱引用值的类。如果不再存在对该值的强引用,则将丢弃字典中的条目。
注意
警告:因为a
WeakValueDictionary
是基于Python字典构建的,所以在迭代它时不能改变大小。这可能难以确保,WeakValueDictionary
因为程序在迭代期间执行的操作可能导致字典中的项目“通过魔法”消失(作为垃圾收集的副作用)。
WeakValueDictionary
对象具有与对象方法具有相同问题的其他keyrefs()
方法WeakKeyDictionary
。
WeakValueDictionary.
valuerefs
()- 返回对值的弱引用的可迭代。
- class
weakref.
WeakSet
([ elements ] ) - 设置保持对其元素的弱引用的类。当不再存在对它的强引用时,将丢弃该元素。
- class
weakref.
WeakMethod
(方法) - 一个自定义
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
垃圾回收期间终结器回调引发的异常将显示在标准错误输出上,但不能传播。它们的处理方式与从对象
__del__()
方法或弱引用的回调引发的异常相同。程序退出时,除非其
atexit
属性设置为false,否则将调用每个剩余的实时终结器。它们以创建的相反顺序被调用。当模块全局变量可能被替换时,终结器永远不会在解释器关闭的后期部分调用它的回调
None
。__call__
()- 如果self还活着,则将其标记为死亡并返回调用结果。如果自我死了然后回来 。
func(*args,**kwargs)
None
detach
()- 如果self还活着,则将其标记为已死并返回元组 。如果自我死了然后回来 。
(obj, func, args,kwargs)
None
peek
()- 如果自己还活着,那么返回元组。如果自我死了然后回来。
(obj, func, args, kwargs)
None
alive
- 如果终结者还活着则属性为true,否则为false。
atexit
- 一个可写的布尔属性,默认情况下为true。当程序退出时,它会调用所有剩余的实时终结器
atexit
。它们以创建的相反顺序被调用。
注意
重要的是确保func,args和kwargs不直接或间接拥有对obj的任何引用,因为否则obj将永远不会被垃圾收集。特别是,func不应该是obj的绑定方法。
版本3.4中的新功能。
weakref.
ReferenceType
- 弱引用对象的类型对象。
weakref.
ProxyType
- 不可调用的对象代理的类型对象。
weakref.
CallableProxyType
- 可调用对象代理的类型对象。
weakref.
ProxyTypes
- 包含代理的所有类型对象的序列。这可以使测试对象是否是代理更简单,而不依赖于命名两种代理类型。
- 异常
weakref.
ReferenceError
- 使用代理对象但已收集基础对象时引发异常。这与标准
ReferenceError
异常相同。
也可以看看
- 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(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: ...