Зачем нужны аннотации типов в Python и что такое Protocol и дженерики?
Короткий ответ
- Аннотации не проверяются в рантайме — это подсказки
- Статическую проверку делают mypy, pyright, ruff
- Optional[X] — это X | None, обязательная проверка на None
- Protocol — структурная типизация, «утиные» интерфейсы
- Дженерики: list[int], dict[str, User], TypeVar / class C[T] (3.12+)
- Pydantic и FastAPI используют аннотации в рантайме
Типизация в Python опциональна и статична: аннотации документируют контракты и ловят ошибки через mypy/pyright, а Protocol даёт интерфейсы без наследования.
Как сказать вслух
пример ответаАннотации типов сам интерпретатор не проверяет — это контракт для статических анализаторов вроде mypy и для читателя кода. Они ловят целый класс ошибок до запуска: передали None туда, где его не ждут, перепутали типы в словаре. Protocol — это структурная типизация: класс подходит под интерфейс, если у него есть нужные методы, наследоваться не нужно. А дженерики позволяют типизировать контейнеры и свои обобщённые классы.
Подробный ответ
Основной ответ
Python остаётся динамическим: аннотации (PEP 484) в рантайме почти не используются интерпретатором, но статические анализаторы (mypy, pyright) проверяют по ним согласованность кода. Современный синтаксис: int | None вместо Optional[int], list[int] вместо List[int], а с Python 3.12 — компактные дженерики def first[T](xs: list[T]) -> T и class Stack[T]. Protocol (PEP 544) описывает интерфейс структурно: любой объект с подходящими методами проходит проверку без наследования — это типизированная «утиная типизация», удобная для DI и тестовых заглушек. Отдельный пласт — рантайм-использование аннотаций: Pydantic валидирует данные по типам, FastAPI строит по ним параметры и документацию, dataclasses генерируют конструкторы.
Ключевые моменты
- Статика, не рантайм. Неверная аннотация не уронит программу — ошибки находит анализатор в CI; поэтому типизацию внедряют постепенно.
- Protocol против ABC. ABC требует явного наследования (номинальная типизация), Protocol сверяет структуру — меньше связность между модулями.
- Дженерики. TypeVar или синтаксис 3.12+ связывают типы входа и выхода: функция, принимающая list[T], возвращает T, и анализатор это отслеживает.
- Типы в рантайме. Pydantic, FastAPI, dataclasses, typer читают аннотации через механизмы интроспекции и строят на них валидацию и API.
Практический контекст
В продакшене типизация — стандарт: mypy или pyright в CI, типизированные сигнатуры в публичных API, Protocol для зависимостей в тестах. Интервьюер проверяет, отличает ли кандидат статическую проверку от рантайма, знает ли Optional-дисциплину и умеет ли типизировать обобщённый код. Частый практический вопрос — чем Protocol лучше ABC для слабой связности.
Пример кода
from typing import Protocol
class Notifier(Protocol):
def send(self, user_id: int, text: str) -> None: ...
class EmailNotifier:
def send(self, user_id: int, text: str) -> None:
print(f"email to {user_id}: {text}")
def notify_all(notifier: Notifier, ids: list[int]) -> None:
for i in ids:
notifier.send(i, "hello")
notify_all(EmailNotifier(), [1, 2]) # наследование не требуетсяЧастые ошибки
- Считают, что Python «проверит типы сам» и аннотации защищают в рантайме
- Пишут Optional-аргументы, но не обрабатывают None по месту использования
- Злоупотребляют Any, сводя пользу статического анализа к нулю