5.15 Keeping References to Bound Methods Without Inhibiting Garbage Collection
Credit: Joseph A. Knapka
5.15.1 Problem
You want to hold bound methods,
while still allowing the associated object to be garbage-collected.
5.15.2 Solution
Weak
references were an important addition to Python 2.1, but
they're not directly usable for bound methods,
unless you take some precautions. To allow an object to be
garbage-collected despite outstanding references to its bound
methods, you need some wrappers. Put the following in the
weakmethod.py file:
import weakref
class _weak_callable:
def _ _init_ _(self, obj, func):
self.im_self = obj
self.im_func = func
def _ _call_ _(self, *args, **kws):
if self.im_self is None:
return self.im_func(*args, **kws)
else:
return self.im_func(self.im_self, *args, **kws)
class WeakMethod:
""" Wraps a function or, more importantly, a bound method in
a way that allows a bound method's object to be GCed, while
providing the same interface as a normal weak reference. """
def _ _init_ _(self, fn):
try:
self._obj = weakref.ref(fn.im_self)
self._meth = fn.im_func
except AttributeError:
# It's not a bound method
self._obj = None
self._meth = fn
def _ _call_ _(self):
if self._dead( ): return None
return _weak_callable(self._getobj( ), self._meth)
def _dead(self):
return self._obj is not None and self._obj( ) is None
def _getobj(self):
if self._obj is None: return None
return self._obj( )
5.15.3 Discussion
A normal bound method holds a strong reference to
the bound method's object. That means that the
object can't be garbage-collected until the bound
method is disposed of:
>>> class C:
... def f(self):
... print "Hello"
... def _ _del_ _(self):
... print "C dying"
...
>>> c = C( )
>>> cf = c.f
>>> del c # c continues to wander about with glazed eyes...
>>> del cf # ...until we stake its bound method, only then it goes away:
C dying
Sometimes that isn't what you want. For example, if
you're implementing an event-dispatch system, it
might not be desirable for the mere presence of an event handler (a
bound method) to prevent the associated object from being reclaimed.
A normal weakref.ref to a bound method
doesn't quite work the way one might expect, because
bound methods are first-class objects. Weak references to bound methods are
dead-on-arrival, i.e., they always return None
when dereferenced, unless another strong reference to the same bound
method exists. The following code, for example,
doesn't print
"Hello" but instead raises an
exception:
>>> from weakref import *
>>> c = C( )
>>> cf = ref(c.f)
>>> cf # Oops, better try the lightning again, Igor...
<weakref at 80ce394; dead>
>>> cf()( )
Traceback (most recent call last):
File "", line 1, in ?
TypeError: object of type 'None' is not callable
WeakMethod
allows you to have weak references to bound methods in a useful way:
>>> from weakmethod import *
>>> cf = WeakMethod(c.f)
>>> cf()( ) # It LIVES! Bwahahahaha!
Hello
>>> del c # ...and it dies
C dying
>>> print cf( )
None
A known problem is that _weak_callable and
WeakMethod don't provide exactly
the same interface as normal callables and weak references. To return
a normal bound method, we can use
new.instancemethod (from the standard module
new), but for that purpose,
WeakMethod should also find out and memorize the
class in which the weakly held bound method is defined.
5.15.4 See Also
The Library Reference section on the
weakref module.
|