I l@ve RuBoard Previous Section Next Section

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.

    I l@ve RuBoard Previous Section Next Section