Welcome, guest | Sign In | My Account | Store | Cart

Reference to a bound method that permits the associated object to be garbage collected.

Python, 42 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File weakmethod.py
from weakref import *

class _weak_callable:

    def __init__(self,obj,func):
        self._obj = obj
        self._meth = func

    def __call__(self,*args,**kws):
        if self._obj is not None:
            return self._meth(self._obj,*args,**kws)
        else:
            return self._meth(*args,**kws)

    def __getattr__(self,attr):
        if attr == 'im_self':
            return self._obj
        if attr == 'im_func':
            return self._meth
        raise AttributeError, attr

class WeakMethod:
    """ Wraps a function or, more importantly, a bound method, in
    a way that allows a bound method's object to be GC'd, while
    providing the same interface as a normal weak reference. """

    def __init__(self,fn):
        try:
            self._obj = 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._obj(),self._meth)

    def _dead(self):
        return self._obj is not None and self._obj() is None

A normal bound method "hides" 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:

<pre>

>>> 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 and arms outstretched...
>>> del cf # ...until we stake its bound method.
C dying
>>>
</pre>

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. Normal weakref.refs to bound methods don't quite work the way one expects, because bound methods are first-class objects; weakrefs to bound methods are dead-on-arrival unless some other strong reference to the same bound method exists. The following code, for example, doesn't print "Hello"; rather it raises an exception:

<pre>

>>> from weakref import *
>>> c = C()
>>> cf = ref(c.f)
>>> cf
&lt;weakref at 80ce394; dead> # Oops, better try the lightning again, Igor...
>>> cf()()
Traceback (most recent call last):
  File "", line 1, in ?
TypeError: object of type 'None' is not callable
>>>
</pre>

WeakMethod allows you to have weak references to bound methods in a useful way:

<pre>

>>> from weakmethod import *
>>> cf = WeakMethod(c.f)
>>> cf()() # It LIVES! Bwahahahaha!
Hello
>>> del c # ...and it dies.
C dying
>>> print cf()
None
>>>
</pre>

Known problems: _weak_callable and WeakMethod don't provide exactly the same interface as normal callables and weak references. There may be a way for WeakMethod to return a normal bound method rather than a _weak_callable, but I haven't yet found it.

PEP 205 discusses the rationale for weak references as they're implemented by the weakref module.

5 comments

Frédéric Jolliton 22 years, 3 months ago  # | flag

Simpler implementation. It is not exactly the same thing, but I think it's simpler for specific uses:

from weakref import ref

class X :
    def f( self , name ) : print 'hi' , name
    def __del__( self ) : print 'Bye'

class WeakMethod :
    def __init__( self , f ) :
        self.f = f.im_func
        self.c = ref( f.im_self )
    def __call__( self , *arg ) :
        if self.c() == None :
            print 'No more object'
            return
        apply( self.f , ( self.c , ) + arg )

x = X()
f = WeakMethod( x.f )
f( 'foo' )
del x
f( 'bar' )

Output is:

hi foo
Bye
No more object
Frédéric Jolliton 22 years, 3 months ago  # | flag

Simpler implementation. Hum, of course it's better to handle error correctly.

Replace

print 'No more object'
return

with

raise TypeError , 'Method called on dead object'
Frédéric Jolliton 22 years, 3 months ago  # | flag

Simpler implementation (without typo). Sorry, I've missed the dereference on self.c. The corrected class is:

import weakref

class WeakMethod :
    def __init__( self , f ) :
        self.f = f.im_func
        self.c = weakref.ref( f.im_self )
    def __call__( self , *arg ) :
        if self.c() == None :
            raise TypeError , 'Method called on dead object'
        apply( self.f , ( self.c() , ) + arg )
Frédéric Jolliton 22 years, 3 months ago  # | flag

Alternative implementation. Here is a version (hopefully the last from me) which support both free/bounded method:

import weakref

class WeakMethodBound :
    def __init__( self , f ) :
        self.f = f.im_func
        self.c = weakref.ref( f.im_self )
    def __call__( self , *arg ) :
        if self.c() == None :
            raise TypeError , 'Method called on dead object'
        apply( self.f , ( self.c() , ) + arg )

class WeakMethodFree :
    def __init__( self , f ) :
        self.f = weakref.ref( f )
    def __call__( self , *arg ) :
        if self.f() == None :
            raise TypeError , 'Function no longer exist'
        apply( self.f() , arg )

def WeakMethod( f ) :
    try :
        f.im_func
    except AttributeError :
        return WeakMethodFree( f )
    return WeakMethodBound( f )

Example:

import WeakMethod

class X :
    def f( self , name ) : print 'hi' , name

def x( name ) : print 'hi' , name

f1 = WeakMethod.WeakMethod( x )
f1( 'foo' )
del x
f1( 'bar' ) # exception

x = X()
f2 = WeakMethod.WeakMethod( x.f )
f2( 'foo' )
del x
f2( 'bar' ) # exception
nicodemus 20 years, 1 month ago  # | flag

Another version. Here's another version, that uses module new to return a true bound-method, and works with functions and unbounded methods also.

import weakref
import new


class ref(object):

    def __init__(self, method):
        try:
            if method.im_self is not None:
                # bound method
                self._obj = weakref.ref(method.im_self)
            else:
                # unbound method
                self._obj = None
            self._func = method.im_func
            self._class = method.im_class
        except AttributeError:
            # not a method
            self._obj = None
            self._func = method
            self._class = None


    def __call__(self):
        '''Return a new bound-method like the original, or the
        original function if refers just to a function or unbound
        method.
        Returns None if the original object doesn't exist
        '''
        if self.is_dead():
            return None
        if self._obj is not None:
            # we have an instance: return a bound method
            return new.instancemethod(self._func, self._obj(), self._class)
        else:
            # we don't have an instance: return just the function
            return self._func


    def is_dead(self):
        '''Returns True if the referenced callable was a bound method and
        the instance no longer exists. Otherwise, return False.
        '''
        return self._obj is not None and self._obj() is None


    def __eq__(self, other):
        try:
            return type(self) is type(other) and self() == other()
        except:
            return False


    def __ne__(self, other):
        return not self == other


Note that calling the ref has the same semanthics as calling a weakref.ref: if the referent died, it returns None. If you want something like weakref.proxy:

class proxy(ref):
    '''Exactly like ref, but calling it will cause the referent method to
    be called with the same arguments. If the referent's object no longer lives,
    ReferenceError is raised.
    '''

    def __call__(self, *args, **kwargs):
        func = ref.__call__(self)
        if func is None:
            raise ReferenceError('object is dead')
        else:
            return func(*args, **kwargs)


    def __eq__(self, other):
        try:
            func1 = ref.__call__(self)
            func2 = ref.__call__(other)
            return type(self) == type(other) and func1 == func2
        except:
            return False
Created by j knapka on Thu, 11 Oct 2001 (PSF)
Python recipes (4591)
j knapka's recipes (2)

Required Modules

Other Information and Tasks