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

Перевод параграфа 6.7 Abstract Base Classes из книги Intermediate Pythonopen in new window.

Чтобы классы реализовывали заданный набор методов в статически типизированных языках, таких как Java, используются интерфейсы и абстрактные классы.

Простая реализация такого контракта в Python — добавить в базовый класс методы по умолчанию, выбрасывающие исключение NotImplementedError. Такое решение неполное: наследники могут не переопределить все методы базового класса, а проблема обнаружится только во время выполнения программы.

Рассмотрим другую ситуацию — использование одного объекта для замещения другого. Заместительopen in new window перехватывает все вызовы и передаёт их в скрываемый объект. Заместитель реализует все нужные методы, но проверку типа через isinstance он не проходит, так как имеет тип отличный от замещаемого объекта.

В Python такие задачи решаются через абстрактные базовые классы, реализуемые модулем abc. Этот модуль определяет мета-класс и набор декораторов. Для определения абстрактного базового класса мы устанавливаем ABCMeta как мета-класс абстрактного класса и помечаем декораторами @abstractmethod и @abstractproperty методы и свойства которые должны быть реализованы в неабстрактных потомках.

Если потомки не реализуют абстрактные методы и свойства то не смогут создавать объекты:

from abc import ABCMeta, abstractmethod

class Vehicle(object):

    __meta-class__ = ABCMeta

    @abstractmethod

    def change_gear(self):
        pass

    @abstractmethod
    def start_engine(self):
        pass

class Car(Vehicle):

    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# abstract methods not implemented
>>> car = Car("Toyota", "Avensis", "silver")

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Car with abstract methods change_gear, start_engine
1
2
3
4
5
6

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

from abc import ABCMeta, abstractmethod

class Vehicle(object):

    __meta-class__ = ABCMeta

    @abstractmethod
    def change_gear(self):
        pass

    @abstractmethod
    def start_engine(self):
        pass

class Car(Vehicle):

    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color

    def change_gear(self):
        print("Changing gear")

    def start_engine(self):
        print("Changing engine")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> car = Car("Toyota", "Avensis", "silver")
>>> print(isinstance(car, Vehicle))
True
1
2
3

Абстрактные классы позволяет регистрировать существующие классы как часть своей иерархии, не проводя проверок на реализацию методов и свойств. Это простое решение второй проблемы открывающей параграф. Абстрактный класс регистрирует класс заместитель и проверка isinstance возвращает корректное значение:

from abc import ABCMeta, abstractmethod

class Vehicle(object):

    __meta-class__ = ABCMeta

    @abstractmethod
    def change_gear(self):
        pass

    @abstractmethod
    def start_engine(self):
        pass

class Car(object):
    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> Vehicle.register(Car)
>>> car = Car("Toyota", "Avensis", "silver")
>>> print(isinstance(car, Vehicle))
True
1
2
3
4

Абстрактные базовые классы широко используются в библиотеке Python. Они предоставляют средство для группировки классов, например, числовых типов, которые имеют относительно плоскую иерархию. Модуль collections также содержит абстрактные базовые классы для различных наборов операций с множествами, последовательностями и словарями. Абстрактные базовые классы Python предоставляют возможность применять контракты между классами такие же как интерфейсы в Java.

Последниее изменение: 24.08.2023, 06:42:55