Как решить проблему нестрогой типизации в Python для указания ожидаемых типов
Что проверяют
Знаете ли вы современную типизацию Python и понимаете ли, где заканчивается её действие. Уточнение почти всегда одно: проверяются ли аннотации во время выполнения.
Как отвечать
Сначала поправьте формулировку: типизация в Python динамическая, но строгая. "1" + 1 падает с TypeError, неявного приведения нет. Задача в другом: указать ожидаемые типы и ловить ошибки до запуска.
- аннотации:
def total(items: list[int]) -> int. С Python 3.9 встроенныеlistиdictработают как дженерики, с 3.10 объединение пишется какint | None.typing.ListиOptionalостались для совместимости, в новом коде они не нужны; - статическая проверка: mypy или pyright в CI и в редакторе. Для нового кода режим
--strictу mypy, для старого постепенное включение по модулям через конфиг; - выразительные типы:
Protocolдля структурной типизации без наследования,TypedDictдля словарей из JSON,Literalдля фиксированных значений,TypeVarили синтаксисdef first[T](xs: list[T]) -> Tиз 3.12; - проверка на границах: аннотации сами ничего не проверяют в рантайме. Данные извне (тело запроса, конфиг, файл) валидирует Pydantic v2, а внутри кода работает статический анализ.
Хорошо упомянуть, что в Python 3.14 аннотации вычисляются лениво, поэтому ссылка на ещё не объявленный класс больше не требует кавычек или from __future__ import annotations.
Где спотыкаются
Считают, что def f(x: int) не пустит строку. Интерпретатор аннотации не проверяет, и без mypy или pyright ошибка всплывёт только в работе.
Глушат проверку через Any, cast и # type: ignore на каждой второй строке. Формально CI зелёный, по сути типов нет. Подавление стоит писать с кодом ошибки, например # type: ignore[arg-type], и с причиной.
Аннотируют всё подряд, включая очевидные локальные переменные. Вывод типов справляется сам, а главную ценность дают сигнатуры публичных функций и структуры данных.