Python Cookbook-6.10 保留对被绑定方法的引用且支持垃圾回收
任务
你想保存一些指向被绑定方法的引用,同时还需要让关联的对象可以被垃圾收集机制处理。
解决方案
弱引用(weakreference)(弱引用在一个对象处于生存期时指向该对象,但如果没有其他正常的引用指向该对象时,这个对象不会被保留)在一些高级编程方法中是一个重要的工具。Python 标准库的 weakref模块允许我们使用弱引用。
然而,weakref的功能无法被直接应用于被绑定方法,除非你采取了一些特殊的预防措施。为了让一个对象在有引用指向其被绑定方法的时候能被垃圾回收机制处理,需要做一些封装工作。将下列代码存为一个名为weakmethod.py 的文件,并放入你的Python的sys.path目录中:
import weakref,new
class ref(object):'''能够封装任何可调用体,特别是被绑定方法,而且被绑定方法仍然能被回收处理。与此同时,提供一个普通的弱引用的接口'''def __init__(self,fn):try:#试图获得对象、函数和类o,f,c = fn.im_self,fn.im_func,fn,im_classexcept AttributeError:#非被绑定方法self._obj = Noneself._func = fnself._clas = Noneelse:#绑定方法if o is None:self._obj = None#…实际上没绑定else: self._obj = weakref.ref(o)#…确实绑定了self._func = fself._clas = cdef __call__(self):if self.obj is None:return self._funcelif self._obj()is None:return Nonereturn new.instancemethod(self._func,self.obj(),self._clas)
讨论
一个正常的被绑定方法拥有一个指向此方法所属对象的强引用。这意味着除非这个被绑定方法被消灭掉,否则该对象不能被当做垃圾重新回收。
>>> 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”,却会抛出一个异常:
>>> 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类则允许你使用指向被绑定方法的弱引用:
>>> 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 类的一个实现:
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 Falsereturn ref.__call__(self) == ref.__call__(other)