Короткий ответ
ABC формализует контракт: класс с абстрактным методом нельзя инстанцировать напрямую, пока все абстрактные методы не переопределены в подклассе, что даёт явную, проверяемую на этапе создания объекта гарантию реализации интерфейса, а не только соглашение по документации, на которое полагается чистая утиная типизация.
Как это работает подробнее
Класс, наследующийся от abc.ABC и помечающий методы @abstractmethod, не может быть создан через ClassName(), если хотя бы один абстрактный метод не переопределён в конкретном подклассе — Python поднимает TypeError прямо в момент попытки создания объекта, а не позже, когда отсутствующий метод впервые понадобится. Это сдвигает обнаружение ошибки "забыли реализовать метод" на самое раннее возможное время.
Пример кода
example.py
from abc import ABC, abstractmethod
class PaymentProvider(ABC):
@abstractmethod
def charge(self, amount: float) -> bool:
...
class CardProvider(PaymentProvider):
def charge(self, amount: float) -> bool:
print(f"Списано {amount} с карты")
return True
try:
PaymentProvider() # TypeError: нельзя создать экземпляр абстрактного класса
except TypeError as e:
print("Ошибка:", e)
CardProvider().charge(100.0) # работает, все абстрактные методы реализованыЧто использовать на практике
- Практика. ABC полезны, когда в команде/библиотеке определён набор обязательных методов для расширяемого компонента (например, разные реализации хранилища данных, разные стратегии оплаты), и важно сразу получить понятную ошибку при неполной реализации, а не падение в продакшене на вызове отсутствующего метода. Утиная типизация без ABC остаётся нормальной практикой для более гибких, неформальных интерфейсов, где избыточная формализация не оправдана.
Подводные камни
- Риски. Добавляют ABC "на всякий случай" почти для всех классов, превращая простой код в избыточно формализованную иерархию. Также забывают, что абстрактный метод может иметь реализацию по умолчанию (вызываемую через
super()), а не только пустое тело — ABC не запрещает частичную реализацию в базовом классе.