/django/dispatch/saferef.py
Python | 250 lines | 241 code | 1 blank | 8 comment | 1 complexity | 8d89fdb16d979ba7d43cb98e02e9c734 MD5 | raw file
1""" 2"Safe weakrefs", originally from pyDispatcher. 3 4Provides a way to safely weakref any function, including bound methods (which 5aren't handled by the core weakref module). 6""" 7 8import weakref, traceback 9 10def safeRef(target, onDelete = None): 11 """Return a *safe* weak reference to a callable target 12 13 target -- the object to be weakly referenced, if it's a 14 bound method reference, will create a BoundMethodWeakref, 15 otherwise creates a simple weakref. 16 onDelete -- if provided, will have a hard reference stored 17 to the callable to be called after the safe reference 18 goes out of scope with the reference object, (either a 19 weakref or a BoundMethodWeakref) as argument. 20 """ 21 if hasattr(target, 'im_self'): 22 if target.im_self is not None: 23 # Turn a bound method into a BoundMethodWeakref instance. 24 # Keep track of these instances for lookup by disconnect(). 25 assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,) 26 reference = get_bound_method_weakref( 27 target=target, 28 onDelete=onDelete 29 ) 30 return reference 31 if callable(onDelete): 32 return weakref.ref(target, onDelete) 33 else: 34 return weakref.ref( target ) 35 36class BoundMethodWeakref(object): 37 """'Safe' and reusable weak references to instance methods 38 39 BoundMethodWeakref objects provide a mechanism for 40 referencing a bound method without requiring that the 41 method object itself (which is normally a transient 42 object) is kept alive. Instead, the BoundMethodWeakref 43 object keeps weak references to both the object and the 44 function which together define the instance method. 45 46 Attributes: 47 key -- the identity key for the reference, calculated 48 by the class's calculateKey method applied to the 49 target instance method 50 deletionMethods -- sequence of callable objects taking 51 single argument, a reference to this object which 52 will be called when *either* the target object or 53 target function is garbage collected (i.e. when 54 this object becomes invalid). These are specified 55 as the onDelete parameters of safeRef calls. 56 weakSelf -- weak reference to the target object 57 weakFunc -- weak reference to the target function 58 59 Class Attributes: 60 _allInstances -- class attribute pointing to all live 61 BoundMethodWeakref objects indexed by the class's 62 calculateKey(target) method applied to the target 63 objects. This weak value dictionary is used to 64 short-circuit creation so that multiple references 65 to the same (object, function) pair produce the 66 same BoundMethodWeakref instance. 67 68 """ 69 70 _allInstances = weakref.WeakValueDictionary() 71 72 def __new__( cls, target, onDelete=None, *arguments,**named ): 73 """Create new instance or return current instance 74 75 Basically this method of construction allows us to 76 short-circuit creation of references to already- 77 referenced instance methods. The key corresponding 78 to the target is calculated, and if there is already 79 an existing reference, that is returned, with its 80 deletionMethods attribute updated. Otherwise the 81 new instance is created and registered in the table 82 of already-referenced methods. 83 """ 84 key = cls.calculateKey(target) 85 current =cls._allInstances.get(key) 86 if current is not None: 87 current.deletionMethods.append( onDelete) 88 return current 89 else: 90 base = super( BoundMethodWeakref, cls).__new__( cls ) 91 cls._allInstances[key] = base 92 base.__init__( target, onDelete, *arguments,**named) 93 return base 94 95 def __init__(self, target, onDelete=None): 96 """Return a weak-reference-like instance for a bound method 97 98 target -- the instance-method target for the weak 99 reference, must have im_self and im_func attributes 100 and be reconstructable via: 101 target.im_func.__get__( target.im_self ) 102 which is true of built-in instance methods. 103 onDelete -- optional callback which will be called 104 when this weak reference ceases to be valid 105 (i.e. either the object or the function is garbage 106 collected). Should take a single argument, 107 which will be passed a pointer to this object. 108 """ 109 def remove(weak, self=self): 110 """Set self.isDead to true when method or instance is destroyed""" 111 methods = self.deletionMethods[:] 112 del self.deletionMethods[:] 113 try: 114 del self.__class__._allInstances[ self.key ] 115 except KeyError: 116 pass 117 for function in methods: 118 try: 119 if callable( function ): 120 function( self ) 121 except Exception, e: 122 try: 123 traceback.print_exc() 124 except AttributeError, err: 125 print '''Exception during saferef %s cleanup function %s: %s'''%( 126 self, function, e 127 ) 128 self.deletionMethods = [onDelete] 129 self.key = self.calculateKey( target ) 130 self.weakSelf = weakref.ref(target.im_self, remove) 131 self.weakFunc = weakref.ref(target.im_func, remove) 132 self.selfName = str(target.im_self) 133 self.funcName = str(target.im_func.__name__) 134 135 def calculateKey( cls, target ): 136 """Calculate the reference key for this reference 137 138 Currently this is a two-tuple of the id()'s of the 139 target object and the target function respectively. 140 """ 141 return (id(target.im_self),id(target.im_func)) 142 calculateKey = classmethod( calculateKey ) 143 144 def __str__(self): 145 """Give a friendly representation of the object""" 146 return """%s( %s.%s )"""%( 147 self.__class__.__name__, 148 self.selfName, 149 self.funcName, 150 ) 151 152 __repr__ = __str__ 153 154 def __nonzero__( self ): 155 """Whether we are still a valid reference""" 156 return self() is not None 157 158 def __cmp__( self, other ): 159 """Compare with another reference""" 160 if not isinstance (other,self.__class__): 161 return cmp( self.__class__, type(other) ) 162 return cmp( self.key, other.key) 163 164 def __call__(self): 165 """Return a strong reference to the bound method 166 167 If the target cannot be retrieved, then will 168 return None, otherwise returns a bound instance 169 method for our object and function. 170 171 Note: 172 You may call this method any number of times, 173 as it does not invalidate the reference. 174 """ 175 target = self.weakSelf() 176 if target is not None: 177 function = self.weakFunc() 178 if function is not None: 179 return function.__get__(target) 180 return None 181 182class BoundNonDescriptorMethodWeakref(BoundMethodWeakref): 183 """A specialized BoundMethodWeakref, for platforms where instance methods 184 are not descriptors. 185 186 It assumes that the function name and the target attribute name are the 187 same, instead of assuming that the function is a descriptor. This approach 188 is equally fast, but not 100% reliable because functions can be stored on an 189 attribute named differenty than the function's name such as in: 190 191 class A: pass 192 def foo(self): return "foo" 193 A.bar = foo 194 195 But this shouldn't be a common use case. So, on platforms where methods 196 aren't descriptors (such as Jython) this implementation has the advantage 197 of working in the most cases. 198 """ 199 def __init__(self, target, onDelete=None): 200 """Return a weak-reference-like instance for a bound method 201 202 target -- the instance-method target for the weak 203 reference, must have im_self and im_func attributes 204 and be reconstructable via: 205 target.im_func.__get__( target.im_self ) 206 which is true of built-in instance methods. 207 onDelete -- optional callback which will be called 208 when this weak reference ceases to be valid 209 (i.e. either the object or the function is garbage 210 collected). Should take a single argument, 211 which will be passed a pointer to this object. 212 """ 213 assert getattr(target.im_self, target.__name__) == target, \ 214 ("method %s isn't available as the attribute %s of %s" % 215 (target, target.__name__, target.im_self)) 216 super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete) 217 218 def __call__(self): 219 """Return a strong reference to the bound method 220 221 If the target cannot be retrieved, then will 222 return None, otherwise returns a bound instance 223 method for our object and function. 224 225 Note: 226 You may call this method any number of times, 227 as it does not invalidate the reference. 228 """ 229 target = self.weakSelf() 230 if target is not None: 231 function = self.weakFunc() 232 if function is not None: 233 # Using curry() would be another option, but it erases the 234 # "signature" of the function. That is, after a function is 235 # curried, the inspect module can't be used to determine how 236 # many arguments the function expects, nor what keyword 237 # arguments it supports, and pydispatcher needs this 238 # information. 239 return getattr(target, function.__name__) 240 return None 241 242def get_bound_method_weakref(target, onDelete): 243 """Instantiates the appropiate BoundMethodWeakRef, depending on the details of 244 the underlying class method implementation""" 245 if hasattr(target, '__get__'): 246 # target method is a descriptor, so the default implementation works: 247 return BoundMethodWeakref(target=target, onDelete=onDelete) 248 else: 249 # no luck, use the alternative implementation: 250 return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)