singledispatch.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. from __future__ import unicode_literals
  7. __all__ = ['singledispatch']
  8. from functools import update_wrapper
  9. from weakref import WeakKeyDictionary
  10. from singledispatch_helpers import MappingProxyType, get_cache_token
  11. ################################################################################
  12. ### singledispatch() - single-dispatch generic function decorator
  13. ################################################################################
  14. def _c3_merge(sequences):
  15. """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
  16. Adapted from http://www.python.org/download/releases/2.3/mro/.
  17. """
  18. result = []
  19. while True:
  20. sequences = [s for s in sequences if s] # purge empty sequences
  21. if not sequences:
  22. return result
  23. for s1 in sequences: # find merge candidates among seq heads
  24. candidate = s1[0]
  25. for s2 in sequences:
  26. if candidate in s2[1:]:
  27. candidate = None
  28. break # reject the current head, it appears later
  29. else:
  30. break
  31. if not candidate:
  32. raise RuntimeError("Inconsistent hierarchy")
  33. result.append(candidate)
  34. # remove the chosen candidate
  35. for seq in sequences:
  36. if seq[0] == candidate:
  37. del seq[0]
  38. def _c3_mro(cls, abcs=None):
  39. """Computes the method resolution order using extended C3 linearization.
  40. If no *abcs* are given, the algorithm works exactly like the built-in C3
  41. linearization used for method resolution.
  42. If given, *abcs* is a list of abstract base classes that should be inserted
  43. into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
  44. result. The algorithm inserts ABCs where their functionality is introduced,
  45. i.e. issubclass(cls, abc) returns True for the class itself but returns
  46. False for all its direct base classes. Implicit ABCs for a given class
  47. (either registered or inferred from the presence of a special method like
  48. __len__) are inserted directly after the last ABC explicitly listed in the
  49. MRO of said class. If two implicit ABCs end up next to each other in the
  50. resulting MRO, their ordering depends on the order of types in *abcs*.
  51. """
  52. for i, base in enumerate(reversed(cls.__bases__)):
  53. if hasattr(base, '__abstractmethods__'):
  54. boundary = len(cls.__bases__) - i
  55. break # Bases up to the last explicit ABC are considered first.
  56. else:
  57. boundary = 0
  58. abcs = list(abcs) if abcs else []
  59. explicit_bases = list(cls.__bases__[:boundary])
  60. abstract_bases = []
  61. other_bases = list(cls.__bases__[boundary:])
  62. for base in abcs:
  63. if issubclass(cls, base) and not any(
  64. issubclass(b, base) for b in cls.__bases__
  65. ):
  66. # If *cls* is the class that introduces behaviour described by
  67. # an ABC *base*, insert said ABC to its MRO.
  68. abstract_bases.append(base)
  69. for base in abstract_bases:
  70. abcs.remove(base)
  71. explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
  72. abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
  73. other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
  74. return _c3_merge(
  75. [[cls]] +
  76. explicit_c3_mros + abstract_c3_mros + other_c3_mros +
  77. [explicit_bases] + [abstract_bases] + [other_bases]
  78. )
  79. def _compose_mro(cls, types):
  80. """Calculates the method resolution order for a given class *cls*.
  81. Includes relevant abstract base classes (with their respective bases) from
  82. the *types* iterable. Uses a modified C3 linearization algorithm.
  83. """
  84. bases = set(cls.__mro__)
  85. # Remove entries which are already present in the __mro__ or unrelated.
  86. def is_related(typ):
  87. return (typ not in bases and hasattr(typ, '__mro__')
  88. and issubclass(cls, typ))
  89. types = [n for n in types if is_related(n)]
  90. # Remove entries which are strict bases of other entries (they will end up
  91. # in the MRO anyway.
  92. def is_strict_base(typ):
  93. for other in types:
  94. if typ != other and typ in other.__mro__:
  95. return True
  96. return False
  97. types = [n for n in types if not is_strict_base(n)]
  98. # Subclasses of the ABCs in *types* which are also implemented by
  99. # *cls* can be used to stabilize ABC ordering.
  100. type_set = set(types)
  101. mro = []
  102. for typ in types:
  103. found = []
  104. for sub in typ.__subclasses__():
  105. if sub not in bases and issubclass(cls, sub):
  106. found.append([s for s in sub.__mro__ if s in type_set])
  107. if not found:
  108. mro.append(typ)
  109. continue
  110. # Favor subclasses with the biggest number of useful bases
  111. found.sort(key=len, reverse=True)
  112. for sub in found:
  113. for subcls in sub:
  114. if subcls not in mro:
  115. mro.append(subcls)
  116. return _c3_mro(cls, abcs=mro)
  117. def _find_impl(cls, registry):
  118. """Returns the best matching implementation from *registry* for type *cls*.
  119. Where there is no registered implementation for a specific type, its method
  120. resolution order is used to find a more generic implementation.
  121. Note: if *registry* does not contain an implementation for the base
  122. *object* type, this function may return None.
  123. """
  124. mro = _compose_mro(cls, registry.keys())
  125. match = None
  126. for t in mro:
  127. if match is not None:
  128. # If *match* is an implicit ABC but there is another unrelated,
  129. # equally matching implicit ABC, refuse the temptation to guess.
  130. if (t in registry and t not in cls.__mro__
  131. and match not in cls.__mro__
  132. and not issubclass(match, t)):
  133. raise RuntimeError("Ambiguous dispatch: {0} or {1}".format(
  134. match, t))
  135. break
  136. if t in registry:
  137. match = t
  138. return registry.get(match)
  139. def singledispatch(func):
  140. """Single-dispatch generic function decorator.
  141. Transforms a function into a generic function, which can have different
  142. behaviours depending upon the type of its first argument. The decorated
  143. function acts as the default implementation, and additional
  144. implementations can be registered using the register() attribute of the
  145. generic function.
  146. """
  147. registry = {}
  148. dispatch_cache = WeakKeyDictionary()
  149. def ns(): pass
  150. ns.cache_token = None
  151. def dispatch(cls):
  152. """generic_func.dispatch(cls) -> <function implementation>
  153. Runs the dispatch algorithm to return the best available implementation
  154. for the given *cls* registered on *generic_func*.
  155. """
  156. if ns.cache_token is not None:
  157. current_token = get_cache_token()
  158. if ns.cache_token != current_token:
  159. dispatch_cache.clear()
  160. ns.cache_token = current_token
  161. try:
  162. impl = dispatch_cache[cls]
  163. except KeyError:
  164. try:
  165. impl = registry[cls]
  166. except KeyError:
  167. impl = _find_impl(cls, registry)
  168. dispatch_cache[cls] = impl
  169. return impl
  170. def register(cls, func=None):
  171. """generic_func.register(cls, func) -> func
  172. Registers a new implementation for the given *cls* on a *generic_func*.
  173. """
  174. if func is None:
  175. return lambda f: register(cls, f)
  176. registry[cls] = func
  177. if ns.cache_token is None and hasattr(cls, '__abstractmethods__'):
  178. ns.cache_token = get_cache_token()
  179. dispatch_cache.clear()
  180. return func
  181. def wrapper(*args, **kw):
  182. return dispatch(args[0].__class__)(*args, **kw)
  183. registry[object] = func
  184. wrapper.register = register
  185. wrapper.dispatch = dispatch
  186. wrapper.registry = MappingProxyType(registry)
  187. wrapper._clear_cache = dispatch_cache.clear
  188. update_wrapper(wrapper, func)
  189. return wrapper