Новый адаптер забыли дописать: как ABC останавливает создание объекта
Приложение выгружает отчёты в разные места. Каждый экспортёр должен уметь записывать содержимое в своё назначение. При добавлении новой реализации легко оставить этот метод недописанным и обнаружить проблему лишь тогда, когда фоновая задача дойдёт до записи. Если обязательный метод в базовом классе просто делает pass, задача может даже завершиться без ошибки, потеряв результат.
Когда реализации принадлежат одному проекту и должны явно наследовать общий интерфейс, подходит ABC. Метод с abstractmethod делает класс абстрактным. Создать экземпляр наследника можно после реализации всех оставшихся абстрактных методов. Ошибка возникает при сборке приложения, раньше обработки конкретного отчёта.
Контракт экспортёра
В примере экспортёр хранит строки в памяти. Такой адаптер полезен для теста координирующей логики: можно проверить, какое содержимое она передала. Реальная запись на диск или в сеть находится в другом наследнике, а вызывающая функция зависит от базового контракта.
from abc import ABC, abstractmethod
class Exporter(ABC):
@abstractmethod
def write(self, text: str) -> None:
raise NotImplementedError
class MemoryExporter(Exporter):
def __init__(self) -> None:
self.rows: list[str] = []
def write(self, text: str) -> None:
self.rows.append(text)
def export_daily(target: Exporter) -> None:
target.write("daily")
target = MemoryExporter()
export_daily(target)
assert target.rows == ["daily"]
print(target.rows)
Проверяем незавершённую реализацию
Запуск файла печатает ['daily']. Попробуйте создать Exporter(): Python выбросит TypeError. Затем удалите write из MemoryExporter и повторите создание: ошибка вернётся. Именно это свойство отличает abstractmethod от соглашения в комментарии. Достаточно Python 3.12, дополнительные пакеты для примера не нужны.
ABC проверяет наличие реализации, но не качество её работы. Метод, который всегда возвращает None и не сохраняет строку, удовлетворит механизму абстрактности. Поэтому для каждого адаптера нужен общий набор поведенческих проверок: записанное значение доступно ожидаемому потребителю, ошибки не теряются, повторный вызов имеет определённый результат.
Сигнатуры также стоит проверять статически. Если наследник принимает несовместимые аргументы, mypy может заметить нарушение подстановки. Сам механизм ABC не сравнивает типы параметров при вызове. В крупном проекте эти два уровня дополняют друг друга: типы следят за формой взаимодействия, тесты — за наблюдаемым эффектом.
Выбирайте наследование, когда оно отражает соглашение между контролируемыми реализациями. Если нужно принимать уже существующие сторонние объекты с подходящими методами, часто удобнее структурный Protocol. Не заставляйте библиотеку наследовать ваш класс только ради одного вызова. Для собственного семейства адаптеров ABC даёт ясную точку расширения и раннюю ошибку незавершённой реализации.
Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
