Сообщить об ошибке.

Абстрактные классы в Python

Абстрактный класс - очень важная концепция объектно-ориентированного программирования. Это хорошая практика принципа "не повторяйся". В большом проекте дублирование кода примерно равно повторному использованию ошибок, и один разработчик не может запомнить детали всех классов. Поэтому очень полезно использовать абстрактный класс для определения общего интерфейса для различных реализаций.

Абстрактный класс имеет некоторые особенности, а именно:

  • Абстрактный класс не содержит всех реализаций методов, необходимых для полной работы, это означает, что он содержит один или несколько абстрактных методов. Абстрактный метод - это только объявление метода, без его подробной реализации.
  • Абстрактный класс предоставляет интерфейс для подклассов, чтобы избежать дублирования кода. Нет смысла создавать экземпляр абстрактного класса.
  • Производный подкласс должен реализовать абстрактные методы для создания конкретного класса, который соответствует интерфейсу, определенному абстрактным классом. Следовательно, экземпляр не может быть создан, пока не будут переопределены все его абстрактные методы.

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

Определение абстрактного класса в Python.

Python поставляется с модулем под названием abc, который предоставляет полезные вещи для абстрактного класса. Абстрактный класс можно определить с помощью класса abc.ABC, а абстрактный метод определить с помощью abc.abstractmethod. ABC - это аббревиатура, сокращение от слов абстрактный базовый класс.

Примечание: Класс не является настоящим абстрактным, если он имеет абстрактные методы, но не наследуется от abc.ABC, это означает, что он может быть создан. Например:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def move(self):
        pass

a = Animal() 
# TypeError: Can't instantiate abstract class Animal with abstract methods move


class Animal():
    @abstractmethod
    def move(self):
        pass

a = Animal()

Определение абстрактного метода абстрактного класса.

Декоратор @abstractmethod может использоваться для объявления абстрактных методов свойств и дескрипторов требует, чтобы метакласс класса был ABCMeta или производным от него. Абстрактный класс не может быть создан, пока не будут переопределены все его абстрактные методы и свойства.

На самом деле абстрактный метод в Python не обязательно должен быть "полностью абстрактным", что отличается от некоторых других объектно-ориентированных языков программирования. Можно определить некоторые общие вещи в абстрактном методе и использовать функцию super() для вызова его в подклассах.

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def move(self):
        print('Animal moves')

class Cat(Animal):
    def move(self):
        super().move()
        print('Cat moves')

c = Cat()
c.move()
# Animal moves
# Cat moves

Как показано в приведенном выше примере, абстрактный метод .move() может содержать некоторые функции и может вызываться подклассом с помощью super(). Хотя у него небольшая реализация, это все еще абстрактный метод, и пользователю необходимо полностью реализовать его в подклассах.

Совместно с декоратором @abstractmethod можно использовать такие декораторы, как @property, @classmethod и @staticmethod. Когда декоратор @abstractmethod применяется в сочетании с другими дескрипторами методов, его следует применять как самый внутренний декоратор

Определение абстрактного метода класса.

from abc import ABC, abstractmethod

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

Определение абстрактного статического метода класса.

from abc import ABC, abstractmethod

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

Определение абстрактного дескриптора класса.

В приведенном примере определяется свойство только для чтения:

from abc import ABC, abstractmethod

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

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

from abc import ABC, abstractmethod

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.update_abstractmethods().

abc.update_abstractmethods(cls):

Новое в версии Python 3.10.

Функция abc.update_abstractmethods(cls) предназначена для пересчета статуса абстракции абстрактного класса. Эту функцию следует вызывать, если абстрактные методы класса были реализованы или изменены после их создания. Обычно эту функцию следует вызывать из декоратора класса.

Возвращает cls, чтобы разрешить использование в качестве декоратора класса.

Если cls не является экземпляром ABC, ничего не делает.

Примечание. Эта функция предполагает, что суперклассы cls уже обновлены. Она не обновляет никаких подклассов.

Общий пример концепции определения абстрактных классов.

from abc import ABC, 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)