1"""Refactored 'safe reference from dispatcher.py""" 2 3import collections 4import traceback 5import weakref 6from functools import total_ordering 7 8 9def safe_ref(target, on_delete=None): 10 """Return a *safe* weak reference to a callable target. 11 12 - ``target``: The object to be weakly referenced, if it's a bound 13 method reference, will create a BoundMethodWeakref, otherwise 14 creates a simple weakref. 15 16 - ``on_delete``: If provided, will have a hard reference stored to 17 the callable to be called after the safe reference goes out of 18 scope with the reference object, (either a weakref or a 19 BoundMethodWeakref) as argument. 20 """ 21 if hasattr(target, "im_self"): 22 if target.__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"), ( 26 f"safe_ref target {target!r} has im_self, but no im_func, " 27 "don't know how to create reference" 28 ) 29 reference = BoundMethodWeakref(target=target, on_delete=on_delete) 30 return reference 31 32 if hasattr(target, "__self__"): 33 if target.__self__ is not None: 34 assert hasattr(target, "__func__"), ( 35 f"safe_ref target {target!r} has __self__, but no __func__, " 36 "don't know how to create reference" 37 ) 38 reference = BoundMethodWeakref(target=target, on_delete=on_delete) 39 return reference 40 41 if hasattr(on_delete, "__call__"): 42 return weakref.ref(target, on_delete) 43 else: 44 return weakref.ref(target) 45 46 47@total_ordering 48class BoundMethodWeakref(object): 49 """'Safe' and reusable weak references to instance methods. 50 51 BoundMethodWeakref objects provide a mechanism for referencing a 52 bound method without requiring that the method object itself 53 (which is normally a transient object) is kept alive. Instead, 54 the BoundMethodWeakref object keeps weak references to both the 55 object and the function which together define the instance method. 56 57 Attributes: 58 59 - ``key``: The identity key for the reference, calculated by the 60 class's calculate_key method applied to the target instance method. 61 62 - ``deletion_methods``: Sequence of callable objects taking single 63 argument, a reference to this object which will be called when 64 *either* the target object or target function is garbage 65 collected (i.e. when this object becomes invalid). These are 66 specified as the on_delete parameters of safe_ref calls. 67 68 - ``weak_self``: Weak reference to the target object. 69 70 - ``weak_func``: Weak reference to the target function. 71 72 Class Attributes: 73 74 - ``_all_instances``: Class attribute pointing to all live 75 BoundMethodWeakref objects indexed by the class's 76 calculate_key(target) method applied to the target objects. 77 This weak value dictionary is used to short-circuit creation so 78 that multiple references to the same (object, function) pair 79 produce the same BoundMethodWeakref instance. 80 """ 81 82 _all_instances = weakref.WeakValueDictionary() 83 84 def __new__(cls, target, on_delete=None, *arguments, **named): 85 """Create new instance or return current instance. 86 87 Basically this method of construction allows us to 88 short-circuit creation of references to already- referenced 89 instance methods. The key corresponding to the target is 90 calculated, and if there is already an existing reference, 91 that is returned, with its deletion_methods attribute updated. 92 Otherwise the new instance is created and registered in the 93 table of already-referenced methods. 94 """ 95 key = cls.calculate_key(target) 96 current = cls._all_instances.get(key) 97 if current is not None: 98 current.deletion_methods.append(on_delete) 99 return current 100 else: 101 base = super(BoundMethodWeakref, cls).__new__(cls) 102 cls._all_instances[key] = base 103 base.__init__(target, on_delete, *arguments, **named) 104 return base 105 106 def __init__(self, target, on_delete=None): 107 """Return a weak-reference-like instance for a bound method. 108 109 - ``target``: The instance-method target for the weak reference, 110 must have im_self and im_func attributes and be 111 reconstructable via the following, which is true of built-in 112 instance methods:: 113 114 target.im_func.__get__( target.im_self ) 115 116 - ``on_delete``: Optional callback which will be called when 117 this weak reference ceases to be valid (i.e. either the 118 object or the function is garbage collected). Should take a 119 single argument, which will be passed a pointer to this 120 object. 121 """ 122 123 def remove(weak, self_=self): 124 """Set self.isDead to True when method or instance is destroyed.""" 125 methods = self_.deletion_methods[:] 126 del self_.deletion_methods[:] 127 try: 128 del self_.__class__._all_instances[self_.key] 129 except KeyError: 130 pass 131 for function in methods: 132 try: 133 if isinstance(function, collections.Callable): 134 function(self_) 135 except Exception: 136 try: 137 traceback.print_exc() 138 except AttributeError as e: 139 print( 140 f"Exception during saferef {self_} " 141 f"cleanup function {function}: {e}" 142 ) 143 144 self.deletion_methods = [on_delete] 145 self.key = self.calculate_key(target) 146 try: 147 self.weak_self = weakref.ref(target.__self__, remove) 148 self.weak_func = weakref.ref(target.__func__, remove) 149 self.self_name = str(target.__self__) 150 self.__name__ = str(target.__func__.__name__) 151 except AttributeError: 152 self.weak_self = weakref.ref(target.__self__, remove) 153 self.weak_func = weakref.ref(target.__func__, remove) 154 self.self_name = str(target.__self__) 155 self.__name__ = str(target.__func__.__name__) 156 157 @classmethod 158 def calculate_key(cls, target): 159 """Calculate the reference key for this reference. 160 161 Currently this is a two-tuple of the id()'s of the target 162 object and the target function respectively. 163 """ 164 return id(target.__self__), id(target.__func__) 165 166 def __str__(self): 167 """Give a friendly representation of the object.""" 168 return f"{self.__class__.__name__}({self.self_name}.{self.__name__})" 169 170 __repr__ = __str__ 171 172 def __bool__(self): 173 """Whether we are still a valid reference.""" 174 return self() is not None 175 176 def __eq__(self, other): 177 """Compare with another reference.""" 178 if not isinstance(other, self.__class__): 179 return self.__class__ is type(other) 180 else: 181 return self.key == other.key 182 183 def __ne__(self, other): 184 """Compare with another reference.""" 185 if not isinstance(other, self.__class__): 186 return self.__class__ is not type(other) 187 else: 188 return self.key != other.key 189 190 def __lt__(self, other): 191 """Compare with another reference.""" 192 if not isinstance(other, self.__class__): 193 return self.__class__ < type(other) 194 else: 195 return self.key < other.key 196 197 def __call__(self): 198 """Return a strong reference to the bound method. 199 200 If the target cannot be retrieved, then will return None, 201 otherwise returns a bound instance method for our object and 202 function. 203 204 Note: You may call this method any number of times, as it does 205 not invalidate the reference. 206 """ 207 target = self.weak_self() 208 if target is not None: 209 function = self.weak_func() 210 if function is not None: 211 return function.__get__(target) 212 return None 213 214 def __hash__(self): 215 return hash(self.key) 216