_py_abc.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. from _weakrefset import WeakSet
  2. def get_cache_token():
  3. """Returns the current ABC cache token.
  4. The token is an opaque object (supporting equality testing) identifying the
  5. current version of the ABC cache for virtual subclasses. The token changes
  6. with every call to ``register()`` on any ABC.
  7. """
  8. return ABCMeta._abc_invalidation_counter
  9. class ABCMeta(type):
  10. """Metaclass for defining Abstract Base Classes (ABCs).
  11. Use this metaclass to create an ABC. An ABC can be subclassed
  12. directly, and then acts as a mix-in class. You can also register
  13. unrelated concrete classes (even built-in classes) and unrelated
  14. ABCs as 'virtual subclasses' -- these and their descendants will
  15. be considered subclasses of the registering ABC by the built-in
  16. issubclass() function, but the registering ABC won't show up in
  17. their MRO (Method Resolution Order) nor will method
  18. implementations defined by the registering ABC be callable (not
  19. even via super()).
  20. """
  21. # A global counter that is incremented each time a class is
  22. # registered as a virtual subclass of anything. It forces the
  23. # negative cache to be cleared before its next use.
  24. # Note: this counter is private. Use `abc.get_cache_token()` for
  25. # external code.
  26. _abc_invalidation_counter = 0
  27. def __new__(mcls, name, bases, namespace, /, **kwargs):
  28. cls = super().__new__(mcls, name, bases, namespace, **kwargs)
  29. # Compute set of abstract method names
  30. abstracts = {name
  31. for name, value in namespace.items()
  32. if getattr(value, "__isabstractmethod__", False)}
  33. for base in bases:
  34. for name in getattr(base, "__abstractmethods__", set()):
  35. value = getattr(cls, name, None)
  36. if getattr(value, "__isabstractmethod__", False):
  37. abstracts.add(name)
  38. cls.__abstractmethods__ = frozenset(abstracts)
  39. # Set up inheritance registry
  40. cls._abc_registry = WeakSet()
  41. cls._abc_cache = WeakSet()
  42. cls._abc_negative_cache = WeakSet()
  43. cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
  44. return cls
  45. def register(cls, subclass):
  46. """Register a virtual subclass of an ABC.
  47. Returns the subclass, to allow usage as a class decorator.
  48. """
  49. if not isinstance(subclass, type):
  50. raise TypeError("Can only register classes")
  51. if issubclass(subclass, cls):
  52. return subclass # Already a subclass
  53. # Subtle: test for cycles *after* testing for "already a subclass";
  54. # this means we allow X.register(X) and interpret it as a no-op.
  55. if issubclass(cls, subclass):
  56. # This would create a cycle, which is bad for the algorithm below
  57. raise RuntimeError("Refusing to create an inheritance cycle")
  58. cls._abc_registry.add(subclass)
  59. ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
  60. return subclass
  61. def _dump_registry(cls, file=None):
  62. """Debug helper to print the ABC registry."""
  63. print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
  64. print(f"Inv. counter: {get_cache_token()}", file=file)
  65. for name in cls.__dict__:
  66. if name.startswith("_abc_"):
  67. value = getattr(cls, name)
  68. if isinstance(value, WeakSet):
  69. value = set(value)
  70. print(f"{name}: {value!r}", file=file)
  71. def _abc_registry_clear(cls):
  72. """Clear the registry (for debugging or testing)."""
  73. cls._abc_registry.clear()
  74. def _abc_caches_clear(cls):
  75. """Clear the caches (for debugging or testing)."""
  76. cls._abc_cache.clear()
  77. cls._abc_negative_cache.clear()
  78. def __instancecheck__(cls, instance):
  79. """Override for isinstance(instance, cls)."""
  80. # Inline the cache checking
  81. subclass = instance.__class__
  82. if subclass in cls._abc_cache:
  83. return True
  84. subtype = type(instance)
  85. if subtype is subclass:
  86. if (cls._abc_negative_cache_version ==
  87. ABCMeta._abc_invalidation_counter and
  88. subclass in cls._abc_negative_cache):
  89. return False
  90. # Fall back to the subclass check.
  91. return cls.__subclasscheck__(subclass)
  92. return any(cls.__subclasscheck__(c) for c in (subclass, subtype))
  93. def __subclasscheck__(cls, subclass):
  94. """Override for issubclass(subclass, cls)."""
  95. if not isinstance(subclass, type):
  96. raise TypeError('issubclass() arg 1 must be a class')
  97. # Check cache
  98. if subclass in cls._abc_cache:
  99. return True
  100. # Check negative cache; may have to invalidate
  101. if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
  102. # Invalidate the negative cache
  103. cls._abc_negative_cache = WeakSet()
  104. cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
  105. elif subclass in cls._abc_negative_cache:
  106. return False
  107. # Check the subclass hook
  108. ok = cls.__subclasshook__(subclass)
  109. if ok is not NotImplemented:
  110. assert isinstance(ok, bool)
  111. if ok:
  112. cls._abc_cache.add(subclass)
  113. else:
  114. cls._abc_negative_cache.add(subclass)
  115. return ok
  116. # Check if it's a direct subclass
  117. if cls in getattr(subclass, '__mro__', ()):
  118. cls._abc_cache.add(subclass)
  119. return True
  120. # Check if it's a subclass of a registered class (recursive)
  121. for rcls in cls._abc_registry:
  122. if issubclass(subclass, rcls):
  123. cls._abc_cache.add(subclass)
  124. return True
  125. # Check if it's a subclass of a subclass (recursive)
  126. for scls in cls.__subclasses__():
  127. if issubclass(subclass, scls):
  128. cls._abc_cache.add(subclass)
  129. return True
  130. # No dice; update negative cache
  131. cls._abc_negative_cache.add(subclass)
  132. return False