Поля конфигурации перепутались местами: keyword-only dataclass
Настройки подключения начинаются с двух строк: адреса основного узла и адреса резервного. Позже добавляются окружение и имя приложения. Вызов Settings("primary", "backup", "prod", "billing") выглядит компактно, но на ревью приходится вспоминать порядок полей. Перестановка двух строк не вызывает исключения и меняет поведение программы.
Для объектов конфигурации имена аргументов часто важнее краткости. Параметр kw_only=True в dataclass заставляет передавать поля по имени. Это правило действует в сгенерированном конструкторе: позиционный вызов заканчивается TypeError. На Python 3.10 и новее такая возможность есть в стандартной библиотеке.
Имена вместо порядка полей
Сделаем объект, где явно видны основная и резервная точки. Заодно сохраним валидацию в __post_init__, чтобы пустой основной адрес не прошёл при обычном создании. Аннотация str сама по себе не проверяет внешние данные. Здесь нас интересуют две разные ошибки: неверная форма вызова и недопустимое значение обязательной настройки.
from dataclasses import dataclass
@dataclass(kw_only=True)
class Settings:
primary: str
fallback: str
environment: str = "dev"
def __post_init__(self) -> None:
if not self.primary:
raise ValueError("primary is required")
settings = Settings(primary="primary", fallback="backup")
assert settings.environment == "dev"
print(settings.primary)
Переход существующих вызовов
Сохраните файл и запустите Python 3.12. Корректный объект напечатает primary. Для проверки защиты от старого вызова отдельно выполните Settings("primary", "backup"): появится TypeError. А Settings(primary="", fallback="backup") дойдёт до __post_init__ и получит ValueError. Эти проверки стоит держать раздельными, чтобы случайная ошибка конструктора не маскировала исчезнувшую валидацию.
При миграции большого проекта сначала найдите все вызовы Settings. Если конструктор используется в нескольких пакетах, переход меняет публичный API и требует обновить потребителей. Для постепенного перехода можно сделать именованной только новую группу полей через dataclasses.KW_ONLY. Это позволяет оставить короткий стабильный позиционный идентификатор и потребовать имена для остальных настроек. Выбор зависит от того, какие вызовы действительно удобно читать.
Не превращайте большой dataclass в универсальную корзину. Если половина полей нужна только одному компоненту, выделите отдельный объект и передавайте его этому компоненту. Имена аргументов устраняют неоднозначность вызова, но не исправляют смешение независимых конфигураций. Маленькие объекты также проще собирать в тестах: видно, какое значение влияет на проверяемый сценарий.
Хорошая дополнительная проверка — добавить новое поле с разумным значением по умолчанию и убедиться, что существующие именованные вызовы не изменились. Затем проверьте сериализацию, если объект сохраняется: требование keyword-only относится к конструктору, а формат хранения нужно поддерживать отдельно. После этого пример создания настройки становится её короткой документацией: автор вызова пишет, какое значение чему соответствует.
Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
