abc — Абстрактные базовые классы


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

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

Данный модуль предоставляет метакласс ABCMeta для определения ABC и вспомогательный класс ABC для альтернативного определения ABC посредством наследования:

class abc.ABC

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

from abc import ABC

class MyABC(ABC):
    pass

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

from abc import ABCMeta

class MyABC(metaclass=ABCMeta):
    pass

Добавлено в версии 3.4.

class abc.ABCMeta

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

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

У классов, созданных с помощью метакласса ABCMeta, есть следующий метод:

register(subclass)

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

from abc import ABC

class MyABC(ABC):
    pass

MyABC.register(tuple)

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

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

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

Вы также можете переопределить данный метод в абстрактном базовом классе:

__subclasshook__(subclass)

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

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

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

Для демонстрации данных концепций взгляните на это определение ABC в качестве примера:

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

class MyIterable(ABC):

    @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, но его не нужно переопределять в неабстрактных производных классах.

Определённый здесь метод класса __subclasshook__() говорит, что любой класс, у которого есть метод __iter__() в своём __dict__ (или в одном из его базовых классов, доступ к которому осуществляется через список __mro__), также считается MyIterable.

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

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

@abc.abstractmethod

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

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

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

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

class C(ABC):
    @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 property делает эквивалент:

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

Примечание

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

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

@abc.abstractclassmethod

Добавлено в версии 3.2.

Не рекомендуется, начиная с версии 3.3: Теперь можно использовать classmethod с abstractmethod(), что делает данный декоратор избыточным.

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

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

class C(ABC):
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...
@abc.abstractstaticmethod

Добавлено в версии 3.2.

Не рекомендуется, начиная с версии 3.3: Теперь можно использовать staticmethod с abstractmethod(), что делает данный декоратор избыточным.

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

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

class C(ABC):
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...
@abc.abstractproperty

Не рекомендуется, начиная с версии 3.3: Теперь можно использовать property, property.getter(), property.setter() и property.deleter() с abstractmethod(), что делает данный декоратор избыточным.

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

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

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

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

class C(ABC):
    @property
    def x(self):
        ...

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

Если только некоторые компоненты являются абстрактными, только данные компоненты необходимо обновить, чтобы создать свойство в подклассе:

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

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

abc.get_cache_token()

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

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

Добавлено в версии 3.4.

Сноски

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