Аннотации типов стали нормой в коммерческом Python: на них держатся FastAPI, Pydantic, SQLAlchemy 2.0, автодополнение в IDE и проверка в CI. На собесе спрашивают, что аннотации дают и чего не дают, и как описать нетривиальный тип.
Аннотации не проверяются в рантайме
def greet(name: str) -> str:
return 'Привет, ' + name
greet(42) # интерпретатор не возражает, упадёт только на сложении
Python хранит аннотации в __annotations__, но сам их не проверяет. Проверяют статические анализаторы: mypy, pyright, а в последнее время и быстрые проверяльщики на Rust. Библиотеки вроде Pydantic читают аннотации и валидируют данные сами.
Базовый синтаксис (3.10+)
def find(users: list[User], user_id: int) -> User | None: ...
config: dict[str, int | str] = {}
pair: tuple[int, str]
many: tuple[int, ...] # кортеж любой длины
callback: Callable[[int, str], bool]
- Встроенные коллекции параметризуются напрямую:
list[int](3.9+), безtyping.List. X | None(3.10+) то же, чтоOptional[X].Optionalзначит «может быть None», а не «необязательный аргумент».Anyотключает проверку.objectнаоборот: принимает всё, но ничего не разрешает без сужения.
Сужение типов
def size(x: int | str) -> int:
if isinstance(x, str):
return len(x) # здесь x: str
return x # здесь x: int
Анализатор понимает isinstance, is None, in, match. Для своих проверок есть TypeGuard и TypeIs (3.13).
Дженерики
С Python 3.12 (PEP 695) появился короткий синтаксис:
def first[T](items: list[T]) -> T:
return items[0]
class Stack[T]:
def __init__(self) -> None:
self.items: list[T] = []
type Json = dict[str, 'Json'] | list['Json'] | str | int | float | bool | None
Раньше то же писали через T = TypeVar('T') и class Stack(Generic[T]), этот стиль ещё встречается в коде.
Protocol: утиная типизация для анализатора
from typing import Protocol
class Closable(Protocol):
def close(self) -> None: ...
def shutdown(items: list[Closable]) -> None:
for x in items:
x.close()
Любой класс с методом close() подходит, наследоваться от Closable не нужно (структурная типизация). Это отличие от abc.ABC, где нужна явная регистрация или наследование.
TypedDict, Literal, Final и другие
TypedDict: словарь с известными ключами и типами значений, например JSON из API.Literal['asc', 'desc']: только эти значения.Final: константа, переприсваивание запрещено.@overload: несколько сигнатур одной функции.ParamSpec: типизация декораторов, сохраняющих сигнатуру.Self(3.11): метод возвращает экземпляр своего класса.
Аннотации и время выполнения
Аннотации раньше вычислялись при определении функции, поэтому ссылка на ещё не объявленный класс требовала строки 'User' или from __future__ import annotations. В 3.14 аннотации вычисляются лениво (PEP 649), и прямые ссылки вперёд работают без кавычек. Для циклических импортов только ради типов используют if TYPE_CHECKING:.
Частые ошибки
- Ожидание, что аннотации проверяются при вызове.
Optional[int]в смысле «аргумент можно не передавать».Anyповсюду: проверка фактически выключена.- Изменяемый тип
list[int]в параметре там, где функция только читает: лучшеSequence[int]илиIterable[int]. list[Animal]не совместим сlist[Dog]: список инвариантен, аSequenceковариантен.
Что спросят на собесе
- Проверяет ли Python типы во время выполнения, кто их проверяет.
- Чем
Optionalотличается от необязательного аргумента, что такоеAnyиobject. - Как написать дженерик-функцию и класс, что такое
TypeVar. - Что такое
Protocolи чем он отличается от ABC. - Зачем
TypedDict,Literal,if TYPE_CHECKING.