В Python добавили команду, но забыли обработчик: проверка через assert_never
У обработчика команд есть два допустимых действия: создать задачу и закрыть её. Пока вариантов мало, цепочка if выглядит законченной. Потом в тип добавляют archive, правят парсер и интерфейс, а старый обработчик остаётся без новой ветки. Код продолжает запускаться, но доходит до конца функции без результата. Ошибка появляется далеко от изменения, которое её вызвало.
Обычный else с ValueError помогает при выполнении, однако не сообщает автору новой команды, какие функции ещё нужно обновить. Для конечного набора вариантов полезнее оставить в последней ветке assert_never. Этот вызов говорит проверяющему типы: сюда не должно оставаться ни одного допустимого значения. Если осталось, разбор неполон. В Python 3.11 функция доступна в typing; ниже используем Python 3.12.
Закрытый набор команд
Начнём с обработчика, в котором все варианты разобраны. Важна аннотация аргумента: если оставить str, проверяющий не сможет доказать, что две строки исчерпывают множество. Literal описывает закрытый набор команд, а не произвольный пользовательский текст. Преобразование внешней строки в такой набор остаётся отдельной задачей парсера.
from typing import Literal, assert_never
Command = Literal["create", "close"]
def handle(command: Command) -> str:
if command == "create":
return "created"
if command == "close":
return "closed"
assert_never(command)
assert handle("close") == "closed"
print(handle("create"))
Проверка после расширения модели
Сохраните пример как command.py и выполните python command.py. Получится created. Затем установите проверяющий командой python -m pip install mypy и запустите python -m mypy --strict command.py. Начальный вариант проходит. Теперь добавьте "archive" в Literal, не меняя тело функции: mypy укажет на аргумент assert_never, потому что в этой точке остаётся Literal['archive'].
Не заменяйте последнюю строку на assert False. Она остановит программу, если выполнение туда попадёт, но не выражает тот же контракт для анализа исчерпывающего разбора. Не стоит и приводить command к Never через cast: это скрывает как раз ту информацию, ради которой добавлена проверка. Исправление состоит в новой содержательной ветке и тесте её результата.
Есть практическая разница между обработкой неизвестного ввода и пропущенным внутренним вариантом. На входе приложения неизвестную строку нужно отклонить понятной ошибкой. Внутри типизированной модели неожиданная команда означает рассогласование кода. Смешав эти случаи в одном широком else, легко заставить новый допустимый сценарий выглядеть как ошибка пользователя.
Такой приём особенно полезен при изменении форматов событий и небольших наборов состояний. Найдите функции, которые разбирают один и тот же союз типов, и добавьте проверку в каждую. После расширения модели список диагностик станет списком мест для доработки. В проверку изменений включите и mypy, и запуск тестов: первый следит за полнотой веток, вторые — за смыслом результата.
Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
