Python Cookbook-6.10 保留对被绑定方法的引用且支持垃圾回收

任务

你想保存一些指向被绑定方法的引用,同时还需要让关联的对象可以被垃圾收集机制处理。

解决方案

弱引用(weakreference)(弱引用在一个对象处于生存期时指向该对象,但如果没有其他正常的引用指向该对象时,这个对象不会被保留)在一些高级编程方法中是一个重要的工具。Python 标准库的 weakref模块允许我们使用弱引用。

然而,weakref的功能无法被直接应用于被绑定方法,除非你采取了一些特殊的预防措施。为了让一个对象在有引用指向其被绑定方法的时候能被垃圾回收机制处理,需要做一些封装工作。将下列代码存为一个名为weakmethod.py 的文件,并放入你的Python的sys.path目录中:

python 复制代码
import weakref,new
class ref(object):
	'''能够封装任何可调用体,特别是被绑定方法,而且被绑
	定方法仍然能被回收处理。与此同时,提供一个普通的弱引用的接口'''
	def __init__(self,fn):
		try:
			#试图获得对象、函数和类
			o,f,c = fn.im_self,fn.im_func,fn,im_class
		except AttributeError:#非被绑定方法
			self._obj = None
			self._func = fn
			self._clas = None
		else:#绑定方法
			if o is None:self._obj = None#...实际上没绑定
			else: self._obj = weakref.ref(o)#...确实绑定了
			self._func = f
			self._clas = c
	def __call__(self):
		if self.obj is None:return self._func
		elif self._obj()is None:return None
		return new.instancemethod(self._func,self.obj(),self._clas)

讨论

一个正常的被绑定方法拥有一个指向此方法所属对象的强引用。这意味着除非这个被绑定方法被消灭掉,否则该对象不能被当做垃圾重新回收。

python 复制代码
>>> class C(object):
	def f(self):
		print "Hello"
	def __del__(self):
		print "C dying"
>>> c = C()
>>> cf = c.f
>>> del c#c眨巴眨巴眼睛活得好好的...
>>> del cf#...直到我们干掉了被绑定的方法,它才终于安心地去了
C dying

很多时候这种行为方式很方便,但有时它却达不到你想要的效果。比如,你想实现一个事件分发的系统,你可能不希望由于一个事件处理者(比如一个被绑定函数)的存在,整个关联对象都无法被回收。因而一个很自然的想法是使用弱引用。不过,一个普通的指向被绑定方法的 weakref.ref并不会按照我们的期望工作,那是因为被绑定方法是一级对象(frst-class objects)。指向被绑定方法的弱引用的保质期总是很短的,在解除引用时它们总是返回None,除非还有另外一个强引用指向了这个被绑定方法。

举个例子,下面的代码基于 Python 标准库 weakref模块,并不会打印"hello",却会抛出一个异常:

python 复制代码
>>> import weakref
>>> c = C()
>>> cf = weakref.ref(c.f)
>>> cf#先瞧瞧这是什么东西...
<weakref at 80ce394; dead>
>>> cf()()
Traceback(most recent call last):
File"", line 1, in ?
TypeError: object of type 'None' is not callable

另一方面,下面展示的在 weakmethod模块中的ref类则允许你使用指向被绑定方法的弱引用:

python 复制代码
>>> import weakmethod
>>> cf = weakmethod.ref(c.f)
>>> cf()()# 哇哦!它是活的!
Hello
>>> del c#...然后就死掉了
C dying
>>> print cf()
None

调用 weakmethod.ref实例,即指向一个被绑定方法的引用,与调用 weakref.ref 指向一个函数对象的实例具有相同的语义:如果引用无效,它返回None;否则就返回引用。事实上,此例中它返回了一个新建的new.instancemethod(它持有一个指向对象的强引用,因此,应当确保不持有这个引用,除非你想让那个对象多存活一段时间)。

注意,本节的解决方案代码的设计很讲究,可以很容易地把任何你想封装的可调用体封入一个 ref实例中,无论是方法(绑定或未绑定的)、函数或其他任何东西。但只有当你试图封装一个被绑定的方法时,它才会有弱引用的语义,对其他的情况,ref就像一个普通(强)引用,一直指向处于生存期的可调用体。这种方式让可以用ref封装任意的可调用体,而无须为任何特殊情况做特殊处理。

如果需要的语义接近于 weakrefproxy 的语义,那就更容易实现了,例如从本节的 ref类派生一个子类。当你调用 proxy时,proxy会用相同的参数调用引用。如果被引用的对象已经消亡,会生成weakref.ReferenceError异常。下面是 proxy 类的一个实现:

python 复制代码
class proxy(ref):
	def __call__(self,*args,**kwargs):
		func = ref.__call__(self)
		if func is None:
			raise weakref,ReferenceError('referent object is dead')
		else:
			return func(*args,**kwargs)
	def __eq__(self,other):
		if type(other) != type(self):
			return False
		return ref.__call__(self) == ref.__call__(other)
相关推荐
hi星尘30 分钟前
深度解析:基于Python的微信小程序自动化操作实现
python·微信小程序·自动化
Doker 多克1 小时前
Django 缓存框架
python·缓存·django
夜夜敲码1 小时前
C语言教程(十八):C 语言共用体详解
c语言·开发语言
大学生亨亨2 小时前
go语言八股文(五)
开发语言·笔记·golang
raoxiaoya2 小时前
同时安装多个版本的golang
开发语言·后端·golang
miracletiger3 小时前
uv 新的包管理工具总结
linux·人工智能·python
cloues break.3 小时前
C++进阶----多态
开发语言·c++
ʚɞ 短腿欧尼3 小时前
关系数据的可视化
python·pycharm·可视化·数据可视化·图表
道剑剑非道3 小时前
QT开发技术【qcustomplot 曲线与鼠标十字功能】
开发语言·qt·计算机外设