saferef.py 11 KB

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