_saferef.py 9.2 KB

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