28.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).
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()). [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: Возвращает зарегистрированный подкласс, что позволяет использовать его в качестве декоратора класса.
Также можно переопределить этот метод в абстрактном базовом классе:
- __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(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, но его не обязательно переопределять в неабстрактных производных классах.
Определённый здесь метод класса __subclasshook__() говорит, что любой класс, имеющий метод __iter__() в своём __dict__ (или в __dict__ одного из его базовых классов, доступном через список __mro__), также считается MyIterable.
Наконец, последняя строка делает Foo виртуальным подклассом MyIterable, даже несмотря на то, что он не определяет метод __iter__() (он использует старый протокол итерирования, определённый через __len__() и __getitem__()). Обратите внимание, что это не сделает get_iterator доступным в качестве метода Foo, поэтому он предоставляется отдельно.
Модуль abc также предоставляет следующие декораторы:
- @abc.abstractmethod¶
Декоратор, указывающий на абстрактные методы.
Использование этого декоратора требует, чтобы метаклассом класса был ABCMeta или производный от него. Класс, у которого метакласс производный от ABCMeta, не может быть инстанциирован, пока не будут переопределены все его абстрактные методы и свойства. Абстрактные методы можно вызывать с помощью любого из обычных механизмов вызова `super`. abstractmethod() может использоваться для объявления абстрактных методов для свойств и дескрипторов.
Динамическое добавление абстрактных методов в класс или попытка изменить статус абстрактности метода или класса после его создания не поддерживаются. abstractmethod() влияет только на подклассы, производные с помощью обычного наследования; «виртуальные подклассы», зарегистрированные методом ABC register(), не затрагиваются.
Когда 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 с abstractmethod(), что делает этот декоратор избыточным.
- @abc.abstractstaticmethod¶
Подкласс встроенного staticmethod(), указывающий на абстрактный статический метод. В остальном он аналогичен abstractmethod().
Этот частный случай устарел, так как декоратор staticmethod() теперь корректно определяется как абстрактный при применении к абстрактному методу:
class C(metaclass=ABCMeta): @staticmethod @abstractmethod def my_abstract_staticmethod(...): ...
Новое в версии 3.2.
Устарело с версии 3.3: теперь staticmethod можно использовать с abstractmethod(), что делает этот декоратор избыточным.
- @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(), property.setter() и property.deleter() можно использовать с abstractmethod(), что делает этот декоратор избыточным.
Сноски
| [1] | Программистам на C++ следует отметить, что концепция виртуального базового класса в Python отличается от C++. |