Каковы плюсы и минусы использования абстрактных классов (ABC) для создания интерфейсов в Python?

Ответ

Абстрактные базовые классы (Abstract Base Classes, ABC) из модуля abc позволяют определять "контракты" для дочерних классов, обязывая их реализовывать определенные методы.

Преимущества

  • Гарантия реализации контракта: С помощью декоратора @abstractmethod можно указать, какие методы обязательны для реализации в подклассах. Попытка создать экземпляр дочернего класса без реализации этих методов вызовет TypeError.
  • Явное наследование: Иерархия классов становится очевидной и легко отслеживаемой. Проверка isinstance() и issubclass() работает ожидаемо.
  • Запрет на создание экземпляров: Нельзя создать объект самого абстрактного класса, что логично, так как он представляет собой неполную реализацию.

Недостатки

  • Жесткая иерархия наследования: Класс должен явно наследоваться от ABC, что создает сильную связь. Это может быть неудобно, если класс уже является частью другой иерархии или если вы не можете изменять его исходный код.
  • Более многословный синтаксис: Требуется импортировать ABC и abstractmethod, а также явно указывать метакласс.

Пример

from abc import ABC, abstractmethod

# Абстрактный класс определяет интерфейс "Хранилище"
class BaseStorage(ABC):
    @abstractmethod
    def save(self, data: dict):
        """Сохраняет данные."""
        pass

    @abstractmethod
    def load(self, item_id: str) -> dict:
        """Загружает данные."""
        pass

# Конкретная реализация интерфейса
class FileStorage(BaseStorage):
    def save(self, data: dict):
        print(f"Сохранение {data} в файл...")

    def load(self, item_id: str) -> dict:
        print(f"Загрузка {item_id} из файла...")
        return {"id": item_id, "data": "some_data"}

# Попытка создать экземпляр без реализации метода вызовет ошибку
class IncompleteStorage(BaseStorage):
    def save(self, data: dict):
        pass
    # Метод load() не реализован

# fs = IncompleteStorage()  # TypeError: Can't instantiate abstract class ...

fs = FileStorage()
fs.save({"id": "123"})

Альтернатива: Protocol

В современном Python для определения интерфейсов часто предпочитают typing.Protocol. Он реализует структурную типизацию (duck typing), проверяя наличие нужных методов без явного наследования, что обеспечивает большую гибкость.