ActiveState Powered by ActiveState

Recipe 81253: WeakMethod


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

Python
 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

Discussion

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.

Comments

  1. 1. At 7:22 a.m. on 10 dec 2001, Frédéric Jolliton said:

    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
    
  2. 2. At 7:44 a.m. on 10 dec 2001, Frédéric Jolliton said:

    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'
    
  3. 3. At 9:32 a.m. on 10 dec 2001, Frédéric Jolliton said:

    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 )
    
  4. 4. At 10:31 a.m. on 10 dec 2001, Frédéric Jolliton said:

    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
    
  5. 5. At 6:21 a.m. on 25 jan 2004, Anonymous said:

    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
    

Sign in to comment