Somehow this needed adding.
This commit is contained in:
parent
f5b46850e6
commit
8518bdc382
|
@ -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
|
Loading…
Reference in New Issue