ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: WeakMethod
Submitter: j knapka (other recipes)
Last Updated: 2001/10/12
Version no: 1.2
Category: OOP

 

4 stars 2 vote(s)


Description:

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

Source: Text Source

# 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:

>>> 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
>>>


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:

>>> from weakref import *
>>> c = C()
>>> cf = ref(c.f)
>>> cf
<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
>>>


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
>>>


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.



Add comment

Number of comments: 5

Simpler implementation, Frédéric Jolliton, 2001/12/10
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

Add comment

Simpler implementation, Frédéric Jolliton, 2001/12/10
Hum, of course it's better to handle error correctly. Replace

    print 'No more object'
    return
with
    raise TypeError , 'Method called on dead object'

Add comment

Simpler implementation (without typo), Frédéric Jolliton, 2001/12/10
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 )

Add comment

Alternative implementation, Frédéric Jolliton, 2001/12/10
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

Add comment

Another version, Not specified Not specified, 2004/01/25
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 


Add comment



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. IPy Notify

4. Changing return value ...

5. Quantum Superposition

6. Pickle objects under ...

7. Generalized delegates ...

8. Reorder a sequence (uses ...

9. Setting Win32 System ...

10. ObjectMerger




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.