Документация Python неофициальный перевод

29.7. abc – Абстрактные базовые классыabc – Abstract Base Classes

Исходный код: Lib/abc.py


Этот модуль предоставляет инфраструктуру для определения абстрактных базовых классов (ABC) в Python, как описано в PEP 3119; см. PEP для информации о том, почему это было добавлено в Python. (См. также PEP 3141 и модуль numbers относительно иерархии типов для чисел на основе ABC.)

Модуль collections содержит несколько конкретных классов, которые наследуются от ABC; их, конечно, можно наследовать дальше. Кроме того, подмодуль collections.abc содержит некоторые ABC, которые можно использовать для проверки, предоставляет ли класс или экземпляр определённый интерфейс, например, является ли он хешируемым или отображением.

Этот модуль предоставляет следующие классы:

class abc.ABCMeta

Метакласс для определения абстрактных базовых классов (ABC).

Используйте этот метакласс для создания ABC. ABC может быть напрямую унаследован, а затем действует как примесь (mix-in). Также можно регистрировать несвязанные конкретные классы (даже встроенные классы) и несвязанные ABC как «виртуальные подклассы» – они и их потомки будут считаться подклассами регистрирующего ABC встроенной функцией issubclass(), но регистрирующий ABC не появится в их MRO (порядке разрешения методов), и реализации методов, определённые регистрирующим ABC, не будут вызываемыми (даже через super()). [1]

Классы, созданные с метаклассом ABCMeta, имеют следующий метод:

register(subclass)

Регистрирует подкласс как «виртуальный подкласс» этого ABC. Например:

from abc import ABCMeta

class MyABC(metaclass=ABCMeta):
    pass

MyABC.register(tuple)

assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)

Изменено в версии 3.3: Возвращает зарегистрированный подкласс, что позволяет использовать его в качестве декоратора класса.

Изменено в версии 3.4: Для обнаружения вызовов register() можно использовать функцию get_cache_token().

Также можно переопределить этот метод в абстрактном базовом классе:

__subclasshook__(subclass)

(Должен быть определён как метод класса.)

Проверяет, считается ли подкласс подклассом этого ABC. Это означает, что можно дополнительно настраивать поведение issubclass без необходимости вызывать register() для каждого класса, который требуется считать подклассом ABC. (Этот метод класса вызывается из метода __subclasscheck__() ABC.)

Этот метод должен возвращать True, False или NotImplemented. Если он возвращает True, то подкласс считается подклассом этого ABC. Если он возвращает False, то подкласс не считается подклассом этого ABC, даже если бы он обычно им был. Если он возвращает NotImplemented, проверка на подкласс продолжается обычным механизмом.

Для демонстрации этих концепций посмотрите на следующий пример определения ABC:

class Foo:
    def __getitem__(self, index):
        ...
    def __len__(self):
        ...
    def get_iterator(self):
        return iter(self)

class MyIterable(metaclass=ABCMeta):

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    def get_iterator(self):
        return self.__iter__()

    @classmethod
    def __subclasshook__(cls, C):
        if cls is MyIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

MyIterable.register(Foo)

ABC MyIterable определяет стандартный метод итерируемых объектов, __iter__(), как абстрактный метод. Приведённую здесь реализацию всё ещё можно вызывать из подклассов. Метод get_iterator() также является частью абстрактного базового класса MyIterable, но его не обязательно переопределять в неабстрактных производных классах.

The __subclasshook__() class method defined here says that any class that has an __iter__() method in its __dict__ (or in that of one of its base classes, accessed via the __mro__ list) is considered a MyIterable too.

Наконец, последняя строка делает Foo виртуальным подклассом MyIterable, даже если он не определяет метод __iter__() (он использует старый протокол итерирования, определённый через __len__() и __getitem__()). Обратите внимание, что это не сделает get_iterator доступным в качестве метода Foo, поэтому он предоставляется отдельно.

class abc.ABC

Вспомогательный класс, метаклассом которого является ABCMeta. С помощью этого класса абстрактный базовый класс можно создать простым наследованием от ABC, избегая иногда запутанного использования метаклассов.

Обратите внимание, что тип ABC по-прежнему ABCMeta, поэтому наследование от ABC требует обычных мер предосторожности в отношении использования метаклассов, поскольку множественное наследование может привести к конфликтам метаклассов.

Новое в версии 3.4.

Модуль abc также предоставляет следующие декораторы:

@abc.abstractmethod

Декоратор, указывающий на абстрактные методы.

Использование этого декоратора требует, чтобы метакласс класса был ABCMeta или производным от него. Класс, метакласс которого является производным от ABCMeta, не может быть инстанцирован, если все его абстрактные методы и свойства не переопределены. Абстрактные методы можно вызывать с помощью любого из обычных механизмов вызова super. abstractmethod() может использоваться для объявления абстрактных методов для свойств и дескрипторов.

Динамическое добавление абстрактных методов в класс или попытка изменить статус абстракции метода или класса после его создания не поддерживаются. abstractmethod() влияет только на подклассы, полученные с помощью обычного наследования; «виртуальные подклассы», зарегистрированные с помощью метода register() ABC, не затрагиваются.

Когда abstractmethod() применяется в комбинации с другими дескрипторами методов, он должен применяться как самый внутренний декоратор, как показано в следующих примерах использования:

class C(metaclass=ABCMeta):
    @abstractmethod
    def my_abstract_method(self, ...):
        ...
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...

    @property
    @abstractmethod
    def my_abstract_property(self):
        ...
    @my_abstract_property.setter
    @abstractmethod
    def my_abstract_property(self, val):
        ...

    @abstractmethod
    def _get_x(self):
        ...
    @abstractmethod
    def _set_x(self, val):
        ...
    x = property(_get_x, _set_x)

Для корректного взаимодействия с механизмом абстрактных базовых классов дескриптор должен идентифицировать себя как абстрактный с помощью __isabstractmethod__. В общем случае этот атрибут должен быть True, если какой-либо из методов, используемых для составления дескриптора, является абстрактным. Например, встроенное свойство Python делает эквивалент следующего:

class Descriptor:
    ...
    @property
    def __isabstractmethod__(self):
        return any(getattr(f, '__isabstractmethod__', False) for
                   f in (self._fget, self._fset, self._fdel))

Примечание

В отличие от абстрактных методов Java, эти абстрактные методы могут иметь реализацию. Эту реализацию можно вызвать через механизм super() из класса, который её переопределяет. Это может быть полезно как конечная точка для вызова super в среде, использующей кооперативное множественное наследование.

@abc.abstractclassmethod

Подкласс встроенного classmethod(), указывающий на абстрактный метод класса. В остальном аналогичен abstractmethod().

Этот частный случай устарел, так как декоратор classmethod() теперь корректно определяется как абстрактный при применении к абстрактному методу:

class C(metaclass=ABCMeta):
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...

Новое в версии 3.2.

Устарело с версии 3.3: Теперь можно использовать classmethod с\nabstractmethod(), что делает этот декоратор избыточным.

@abc.abstractstaticmethod

Подкласс встроенного staticmethod(), указывающий на абстрактный статический метод. В остальном аналогичен abstractmethod().

Этот частный случай устарел, так как декоратор staticmethod() теперь корректно определяется как абстрактный при применении к абстрактному методу:

class C(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...

Новое в версии 3.2.

Устарело с версии 3.3: Теперь можно использовать staticmethod с\nabstractmethod(), что делает этот декоратор избыточным.

@abc.abstractproperty(fget=None, fset=None, fdel=None, doc=None)

Подкласс встроенного property(), указывающий на абстрактное свойство.

Использование этой функции требует, чтобы метакласс класса был ABCMeta или был производным от него. Класс, метакласс которого является производным от ABCMeta, не может быть инстанцирован, если все его абстрактные методы и свойства не переопределены. Абстрактные свойства можно вызывать с помощью любого из обычных механизмов вызова «super».

Этот частный случай устарел, так как декоратор property() теперь корректно определяется как абстрактный при применении к абстрактному методу:

class C(metaclass=ABCMeta):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

В приведённом выше примере определяется свойство только для чтения; можно также определить абстрактное свойство для чтения и записи, пометив один или несколько базовых методов как абстрактные.

class C(metaclass=ABCMeta):
    @property
    def x(self):
        ...

    @x.setter
    @abstractmethod
    def x(self, val):
        ...

Если абстрактны только некоторые компоненты, то для создания конкретного свойства в подклассе нужно обновить только эти компоненты.

class D(C):
    @C.x.setter
    def x(self, val):
        ...

Устарело с версии 3.3: Теперь можно использовать property, property.getter(),\nproperty.setter() и property.deleter() с\nabstractmethod(), что делает этот декоратор избыточным.

Модуль abc также предоставляет следующие функции:

abc.get_cache_token()

Возвращает текущий токен кэша абстрактных базовых классов.

Токен – это непрозрачный объект (поддерживающий проверку на равенство), идентифицирующий текущую версию кэша абстрактных базовых классов для виртуальных подклассов. Токен изменяется при каждом вызове ABCMeta.register() для любого ABC.

Новое в версии 3.4.

Сноски

[1]Программистам на C++ следует отметить, что концепция виртуального базового класса в Python отличается от C++.