1# extracted from Louie, http://pylouie.org/ 2# updated for Python 3 3# 4# Copyright (c) 2006 Patrick K. O'Brien, Mike C. Fletcher, 5# Matthew R. Scott 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions are 9# met: 10# 11# * Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# 14# * Redistributions in binary form must reproduce the above 15# copyright notice, this list of conditions and the following 16# disclaimer in the documentation and/or other materials provided 17# with the distribution. 18# 19# * Neither the name of the <ORGANIZATION> nor the names of its 20# contributors may be used to endorse or promote products derived 21# from this software without specific prior written permission. 22# 23# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34# 35"""Refactored 'safe reference from dispatcher.py""" 36 37import operator 38import sys 39import traceback 40import weakref 41 42 43try: 44 callable 45except NameError: 46 def callable(object): 47 return hasattr(object, '__call__') 48 49 50if sys.version_info < (3,): 51 get_self = operator.attrgetter('im_self') 52 get_func = operator.attrgetter('im_func') 53else: 54 get_self = operator.attrgetter('__self__') 55 get_func = operator.attrgetter('__func__') 56 57 58def safe_ref(target, on_delete=None): 59 """Return a *safe* weak reference to a callable target. 60 61 - ``target``: The object to be weakly referenced, if it's a bound 62 method reference, will create a BoundMethodWeakref, otherwise 63 creates a simple weakref. 64 65 - ``on_delete``: If provided, will have a hard reference stored to 66 the callable to be called after the safe reference goes out of 67 scope with the reference object, (either a weakref or a 68 BoundMethodWeakref) as argument. 69 """ 70 try: 71 im_self = get_self(target) 72 except AttributeError: 73 if callable(on_delete): 74 return weakref.ref(target, on_delete) 75 else: 76 return weakref.ref(target) 77 else: 78 if im_self is not None: 79 # Turn a bound method into a BoundMethodWeakref instance. 80 # Keep track of these instances for lookup by disconnect(). 81 assert hasattr(target, 'im_func') or hasattr(target, '__func__'), ( 82 "safe_ref target %r has im_self, but no im_func, " 83 "don't know how to create reference" % target) 84 reference = BoundMethodWeakref(target=target, on_delete=on_delete) 85 return reference 86 87 88class BoundMethodWeakref(object): 89 """'Safe' and reusable weak references to instance methods. 90 91 BoundMethodWeakref objects provide a mechanism for referencing a 92 bound method without requiring that the method object itself 93 (which is normally a transient object) is kept alive. Instead, 94 the BoundMethodWeakref object keeps weak references to both the 95 object and the function which together define the instance method. 96 97 Attributes: 98 99 - ``key``: The identity key for the reference, calculated by the 100 class's calculate_key method applied to the target instance method. 101 102 - ``deletion_methods``: Sequence of callable objects taking single 103 argument, a reference to this object which will be called when 104 *either* the target object or target function is garbage 105 collected (i.e. when this object becomes invalid). These are 106 specified as the on_delete parameters of safe_ref calls. 107 108 - ``weak_self``: Weak reference to the target object. 109 110 - ``weak_func``: Weak reference to the target function. 111 112 Class Attributes: 113 114 - ``_all_instances``: Class attribute pointing to all live 115 BoundMethodWeakref objects indexed by the class's 116 calculate_key(target) method applied to the target objects. 117 This weak value dictionary is used to short-circuit creation so 118 that multiple references to the same (object, function) pair 119 produce the same BoundMethodWeakref instance. 120 """ 121 122 _all_instances = weakref.WeakValueDictionary() 123 124 def __new__(cls, target, on_delete=None, *arguments, **named): 125 """Create new instance or return current instance. 126 127 Basically this method of construction allows us to 128 short-circuit creation of references to already- referenced 129 instance methods. The key corresponding to the target is 130 calculated, and if there is already an existing reference, 131 that is returned, with its deletion_methods attribute updated. 132 Otherwise the new instance is created and registered in the 133 table of already-referenced methods. 134 """ 135 key = cls.calculate_key(target) 136 current = cls._all_instances.get(key) 137 if current is not None: 138 current.deletion_methods.append(on_delete) 139 return current 140 else: 141 base = super(BoundMethodWeakref, cls).__new__(cls) 142 cls._all_instances[key] = base 143 base.__init__(target, on_delete, *arguments, **named) 144 return base 145 146 def __init__(self, target, on_delete=None): 147 """Return a weak-reference-like instance for a bound method. 148 149 - ``target``: The instance-method target for the weak reference, 150 must have im_self and im_func attributes and be 151 reconstructable via the following, which is true of built-in 152 instance methods:: 153 154 target.im_func.__get__( target.im_self ) 155 156 - ``on_delete``: Optional callback which will be called when 157 this weak reference ceases to be valid (i.e. either the 158 object or the function is garbage collected). Should take a 159 single argument, which will be passed a pointer to this 160 object. 161 """ 162 def remove(weak, self=self): 163 """Set self.isDead to True when method or instance is destroyed.""" 164 methods = self.deletion_methods[:] 165 del self.deletion_methods[:] 166 try: 167 del self.__class__._all_instances[self.key] 168 except KeyError: 169 pass 170 for function in methods: 171 try: 172 if callable(function): 173 function(self) 174 except Exception: 175 try: 176 traceback.print_exc() 177 except AttributeError: 178 e = sys.exc_info()[1] 179 print ('Exception during saferef %s ' 180 'cleanup function %s: %s' % (self, function, e)) 181 self.deletion_methods = [on_delete] 182 self.key = self.calculate_key(target) 183 im_self = get_self(target) 184 im_func = get_func(target) 185 self.weak_self = weakref.ref(im_self, remove) 186 self.weak_func = weakref.ref(im_func, remove) 187 self.self_name = str(im_self) 188 self.func_name = str(im_func.__name__) 189 190 def calculate_key(cls, target): 191 """Calculate the reference key for this reference. 192 193 Currently this is a two-tuple of the id()'s of the target 194 object and the target function respectively. 195 """ 196 return (id(get_self(target)), id(get_func(target))) 197 calculate_key = classmethod(calculate_key) 198 199 def __str__(self): 200 """Give a friendly representation of the object.""" 201 return "%s(%s.%s)" % ( 202 self.__class__.__name__, 203 self.self_name, 204 self.func_name, 205 ) 206 207 __repr__ = __str__ 208 209 def __nonzero__(self): 210 """Whether we are still a valid reference.""" 211 return self() is not None 212 213 def __cmp__(self, other): 214 """Compare with another reference.""" 215 if not isinstance(other, self.__class__): 216 return cmp(self.__class__, type(other)) 217 return cmp(self.key, other.key) 218 219 def __call__(self): 220 """Return a strong reference to the bound method. 221 222 If the target cannot be retrieved, then will return None, 223 otherwise returns a bound instance method for our object and 224 function. 225 226 Note: You may call this method any number of times, as it does 227 not invalidate the reference. 228 """ 229 target = self.weak_self() 230 if target is not None: 231 function = self.weak_func() 232 if function is not None: 233 return function.__get__(target) 234 return None 235