|
|
 |
|
Title: WeakMethod
Submitter: j knapka
(other recipes)
Last Updated: 2001/10/12
Version no: 1.2
Category:
OOP
|
|
2 vote(s)
|
|
|
|
Description:
Reference to a bound method that permits the associated object
to be garbage collected.
Source: Text Source
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:
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
|
|
|
|
|
 |
|