diff --git a/Lib/abc.py b/Lib/abc.py new file mode 100644 index 00000000000..ca6be96496c --- /dev/null +++ b/Lib/abc.py @@ -0,0 +1,179 @@ +# Copyright 2007 Google, Inc. All Rights Reserved. +# Licensed to PSF under a Contributor Agreement. + +"""Abstract Base Classes (ABCs) according to PEP 3119.""" + + +def abstractmethod(funcobj): + """A decorator indicating abstract methods. + + Requires that the metaclass is ABCMeta or derived from it. A + class that has a metaclass derived from ABCMeta cannot be + instantiated unless all of its abstract methods are overridden. + The abstract methods can be called using any of the the normal + 'super' call mechanisms. + + Usage: + + class C(metaclass=ABCMeta): + @abstractmethod + def my_abstract_method(self, ...): + ... + """ + funcobj.__isabstractmethod__ = True + return funcobj + + +class _Abstract(object): + + """Helper class inserted into the bases by ABCMeta (using _fix_bases()). + + You should never need to explicitly subclass this class. + + There should never be a base class between _Abstract and object. + """ + + def __new__(cls, *args, **kwds): + am = cls.__dict__.get("__abstractmethods__") + if am: + raise TypeError("Can't instantiate abstract class %s " + "with abstract methods %s" % + (cls.__name__, ", ".join(sorted(am)))) + if (args or kwds) and cls.__init__ is object.__init__: + raise TypeError("Can't pass arguments to __new__ " + "without overriding __init__") + return object.__new__(cls) + + @classmethod + def __subclasshook__(cls, subclass): + """Abstract classes can override this to customize issubclass(). + + This is invoked early on by __subclasscheck__() below. It + should return True, False or NotImplemented. If it returns + NotImplemented, the normal algorithm is used. Otherwise, it + overrides the normal algorithm (and the outcome is cached). + """ + return NotImplemented + + +def _fix_bases(bases): + """Helper method that inserts _Abstract in the bases if needed.""" + for base in bases: + if issubclass(base, _Abstract): + # _Abstract is already a base (maybe indirectly) + return bases + if object in bases: + # Replace object with _Abstract + return tuple([_Abstract if base is object else base + for base in bases]) + # Append _Abstract to the end + return bases + (_Abstract,) + + +class ABCMeta(type): + + """Metaclass for defining Abstract Base Classes (ABCs). + + Use this metaclass to create an ABC. An ABC can be subclassed + directly, and then acts as a mix-in class. You can also register + unrelated concrete classes (even built-in classes) and unrelated + ABCs as 'virtual subclasses' -- these and their descendants will + be considered subclasses of the registering ABC by the built-in + issubclass() function, but the registering ABC won't show up in + their MRO (Method Resolution Order) nor will method + implementations defined by the registering ABC be callable (not + even via super()). + + """ + + # A global counter that is incremented each time a class is + # registered as a virtual subclass of anything. It forces the + # negative cache to be cleared before its next use. + __invalidation_counter = 0 + + def __new__(mcls, name, bases, namespace): + bases = _fix_bases(bases) + cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace) + # Compute set of abstract method names + abstracts = {name + for name, value in namespace.items() + if getattr(value, "__isabstractmethod__", False)} + for base in bases: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = abstracts + # Set up inheritance registry + cls.__registry = set() + cls.__cache = set() + cls.__negative_cache = set() + cls.__negative_cache_version = ABCMeta.__invalidation_counter + return cls + + def register(cls, subclass): + """Register a virtual subclass of an ABC.""" + if not isinstance(cls, type): + raise TypeError("Can only register classes") + if issubclass(subclass, cls): + return # Already a subclass + # Subtle: test for cycles *after* testing for "already a subclass"; + # this means we allow X.register(X) and interpret it as a no-op. + if issubclass(cls, subclass): + # This would create a cycle, which is bad for the algorithm below + raise RuntimeError("Refusing to create an inheritance cycle") + cls.__registry.add(subclass) + ABCMeta.__invalidation_counter += 1 # Invalidate negative cache + + def _dump_registry(cls, file=None): + """Debug helper to print the ABC registry.""" + print("Class: %s.%s" % (cls.__module__, cls.__name__), file=file) + print("Inv.counter: %s" % ABCMeta.__invalidation_counter, file=file) + for name in sorted(cls.__dict__.keys()): + if name.startswith("__abc_"): + value = getattr(cls, name) + print("%s: %r" % (name, value), file=file) + + def __instancecheck__(cls, instance): + """Override for isinstance(instance, cls).""" + return any(cls.__subclasscheck__(c) + for c in {instance.__class__, type(instance)}) + + def __subclasscheck__(cls, subclass): + """Override for issubclass(subclass, cls).""" + # Check cache + if subclass in cls.__cache: + return True + # Check negative cache; may have to invalidate + if cls.__negative_cache_version < ABCMeta.__invalidation_counter: + # Invalidate the negative cache + cls.__negative_cache_version = ABCMeta.__invalidation_counter + cls.__negative_cache = set() + elif subclass in cls.__negative_cache: + return False + # Check the subclass hook + ok = cls.__subclasshook__(subclass) + if ok is not NotImplemented: + assert isinstance(ok, bool) + if ok: + cls.__cache.add(subclass) + else: + cls.__negative_cache.add(subclass) + return ok + # Check if it's a direct subclass + if cls in subclass.__mro__: + cls.__cache.add(subclass) + return True + # Check if it's a subclass of a registered class (recursive) + for rcls in cls.__registry: + if issubclass(subclass, rcls): + cls.__registry.add(subclass) + return True + # Check if it's a subclass of a subclass (recursive) + for scls in cls.__subclasses__(): + if issubclass(subclass, scls): + cls.__registry.add(subclass) + return True + # No dice; update negative cache + cls.__negative_cache.add(subclass) + return False