mirror of https://github.com/python/cpython
212 lines
7.5 KiB
Python
212 lines
7.5 KiB
Python
# Copyright 2007 Google, Inc. All Rights Reserved.
|
|
# Licensed to PSF under a Contributor Agreement.
|
|
|
|
"""Abstract Base Classes (ABCs) according to PEP 3119."""
|
|
|
|
from _weakrefset import WeakSet
|
|
|
|
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 normal
|
|
'super' call mechanisms.
|
|
|
|
Usage:
|
|
|
|
class C(metaclass=ABCMeta):
|
|
@abstractmethod
|
|
def my_abstract_method(self, ...):
|
|
...
|
|
"""
|
|
funcobj.__isabstractmethod__ = True
|
|
return funcobj
|
|
|
|
|
|
class abstractclassmethod(classmethod):
|
|
"""A decorator indicating abstract classmethods.
|
|
|
|
Similar to abstractmethod.
|
|
|
|
Usage:
|
|
|
|
class C(metaclass=ABCMeta):
|
|
@abstractclassmethod
|
|
def my_abstract_classmethod(cls, ...):
|
|
...
|
|
"""
|
|
|
|
__isabstractmethod__ = True
|
|
|
|
def __init__(self, callable):
|
|
callable.__isabstractmethod__ = True
|
|
super().__init__(callable)
|
|
|
|
|
|
class abstractstaticmethod(staticmethod):
|
|
"""A decorator indicating abstract staticmethods.
|
|
|
|
Similar to abstractmethod.
|
|
|
|
Usage:
|
|
|
|
class C(metaclass=ABCMeta):
|
|
@abstractstaticmethod
|
|
def my_abstract_staticmethod(...):
|
|
...
|
|
"""
|
|
|
|
__isabstractmethod__ = True
|
|
|
|
def __init__(self, callable):
|
|
callable.__isabstractmethod__ = True
|
|
super().__init__(callable)
|
|
|
|
|
|
class abstractproperty(property):
|
|
"""A decorator indicating abstract properties.
|
|
|
|
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 properties are overridden.
|
|
The abstract properties can be called using any of the normal
|
|
'super' call mechanisms.
|
|
|
|
Usage:
|
|
|
|
class C(metaclass=ABCMeta):
|
|
@abstractproperty
|
|
def my_abstract_property(self):
|
|
...
|
|
|
|
This defines a read-only property; you can also define a read-write
|
|
abstract property using the 'long' form of property declaration:
|
|
|
|
class C(metaclass=ABCMeta):
|
|
def getx(self): ...
|
|
def setx(self, value): ...
|
|
x = abstractproperty(getx, setx)
|
|
"""
|
|
__isabstractmethod__ = True
|
|
|
|
|
|
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.
|
|
_abc_invalidation_counter = 0
|
|
|
|
def __new__(mcls, name, bases, namespace):
|
|
cls = super().__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__ = frozenset(abstracts)
|
|
# Set up inheritance registry
|
|
cls._abc_registry = WeakSet()
|
|
cls._abc_cache = WeakSet()
|
|
cls._abc_negative_cache = WeakSet()
|
|
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
|
|
return cls
|
|
|
|
def register(cls, subclass):
|
|
"""Register a virtual subclass of an ABC."""
|
|
if not isinstance(subclass, 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._abc_registry.add(subclass)
|
|
ABCMeta._abc_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._abc_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)."""
|
|
# Inline the cache checking
|
|
subclass = instance.__class__
|
|
if subclass in cls._abc_cache:
|
|
return True
|
|
subtype = type(instance)
|
|
if subtype is subclass:
|
|
if (cls._abc_negative_cache_version ==
|
|
ABCMeta._abc_invalidation_counter and
|
|
subclass in cls._abc_negative_cache):
|
|
return False
|
|
# Fall back to the subclass check.
|
|
return cls.__subclasscheck__(subclass)
|
|
return any(cls.__subclasscheck__(c) for c in {subclass, subtype})
|
|
|
|
def __subclasscheck__(cls, subclass):
|
|
"""Override for issubclass(subclass, cls)."""
|
|
# Check cache
|
|
if subclass in cls._abc_cache:
|
|
return True
|
|
# Check negative cache; may have to invalidate
|
|
if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
|
|
# Invalidate the negative cache
|
|
cls._abc_negative_cache = WeakSet()
|
|
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
|
|
elif subclass in cls._abc_negative_cache:
|
|
return False
|
|
# Check the subclass hook
|
|
ok = cls.__subclasshook__(subclass)
|
|
if ok is not NotImplemented:
|
|
assert isinstance(ok, bool)
|
|
if ok:
|
|
cls._abc_cache.add(subclass)
|
|
else:
|
|
cls._abc_negative_cache.add(subclass)
|
|
return ok
|
|
# Check if it's a direct subclass
|
|
if cls in getattr(subclass, '__mro__', ()):
|
|
cls._abc_cache.add(subclass)
|
|
return True
|
|
# Check if it's a subclass of a registered class (recursive)
|
|
for rcls in cls._abc_registry:
|
|
if issubclass(subclass, rcls):
|
|
cls._abc_cache.add(subclass)
|
|
return True
|
|
# Check if it's a subclass of a subclass (recursive)
|
|
for scls in cls.__subclasses__():
|
|
if issubclass(subclass, scls):
|
|
cls._abc_cache.add(subclass)
|
|
return True
|
|
# No dice; update negative cache
|
|
cls._abc_negative_cache.add(subclass)
|
|
return False
|