Django-Admin.ru

Аннотации типов и typing в Python

Python typing на примерах: аннотации переменных и функций, list[int], Optional, TypedDict, Protocol, дженерики, оператор type и проверка кода в mypy.

Редакция Django-Admin.ru
· 20 мин чтения

Содержание · 13 разделов
  1. Зачем нужны аннотации типов
  2. Синтаксис аннотаций
  3. Переменные
  4. Параметры и результат функции
  5. Атрибуты классов
  6. Коллекции: list[int], dict[str, int], tuple
  7. Старый и новый синтаксис
  8. Несколько типов и None: Union и Optional
  9. Модуль typing: основные конструкции
  10. Any и object
  11. Literal и Final
  12. TypedDict: словарь с известными ключами
  13. Protocol: утиная типизация
  14. Self и @override
  15. Annotated: метаданные для библиотек
  16. NewType: разные типы на основе одного
  17. TypeIs: сужение типа своей функцией
  18. Псевдонимы типов: оператор type
  19. type[User] и оператор type
  20. Дженерики: TypeVar и синтаксис Python 3.12
  21. Отложенные аннотации в Python 3.14
  22. Что нового в typing в Python 3.15
  23. Как проверить типы: mypy, pyright, ty
  24. Аннотации во время выполнения
  25. Частые вопросы
  26. Ускоряют или замедляют аннотации программу?
  27. Нужно ли импортировать List, Dict и Optional из typing?
  28. Как аннотировать функцию, которая ничего не возвращает?
  29. Аннотации загромождают код. Можно вынести их отдельно?
  30. Чем TypedDict отличается от dataclass?
  31. Какой анализатор выбрать?
  32. Итог

Аннотации типов в Python — подсказки, какие значения ожидаются в переменной, параметре функции и результате: name: str, -> int. Простые типы (int, str, list[int], int | None) пишутся без импорта, а модуль typing добавляет конструкции для сложных случаев: TypedDict, Protocol, Literal, Self, Annotated. Сам интерпретатор аннотации не проверяет — это делают анализаторы вроде mypy и редактор кода:

def greet(name: str, times: int = 1) -> str:
    return ", ".join([f"Привет, {name}"] * times)

print(greet("Анна", 2))  # → Привет, Анна, Привет, Анна
print(greet(42))         # → Привет, 42 — Python не возражает
$ mypy main.py
main.py:5: error: Argument 1 to "greet" has incompatible type "int"; expected "str"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Ниже — синтаксис аннотаций, основные инструменты typing, дженерики, оператор type и изменения в Python 3.12–3.15. Примеры проверены на Python 3.14 и mypy 2.4, примеры для 3.15 — на Python 3.15. Базовый синтаксис языка собран в шпаргалке по синтаксису Python.

Зачем нужны аннотации типов

Python — язык с динамической типизацией: тип есть у значения, а переменная может ссылаться на что угодно. Ошибку вида «передали строку вместо числа» вы увидите только при запуске, и то если код дойдёт до проблемной строки. Аннотации переносят часть таких ошибок на этап до запуска.

Что они дают на практике:

  • Проверку до запуска. mypy, pyright или ty читают аннотации и находят несовместимые типы, забытую проверку на None, опечатки в атрибутах.
  • Подсказки в редакторе. VS Code и PyCharm по аннотациям показывают методы объекта и подсвечивают ошибки прямо при наборе.
  • Документацию в коде. Сигнатура def send(to: str, retries: int = 3) -> bool объясняет функцию лучше комментария и не устаревает незаметно.
  • Работу библиотек. Pydantic проверяет данные по аннотациям полей, FastAPI строит из них валидацию запросов и документацию API, dataclasses генерирует __init__.

Аннотации необязательны: можно добавлять их постепенно, начиная с публичных функций. Неаннотированный код mypy по умолчанию не проверяет, поэтому старый проект не «покраснеет» целиком после первого запуска.

Синтаксис аннотаций

Переменные

Тип пишется после имени через двоеточие. Значение можно не присваивать — тогда это только объявление:

count: int = 0
names: list[str] = []
user_id: int          # объявлена, но не создана
print(count, names)   # → 0 []

Аннотация переменной не создаёт её: обращение к user_id до присваивания вызовет NameError. Подробнее о самих переменных и встроенных типах — в статье о переменных и типах данных.

Параметры и результат функции

Тип параметра — после двоеточия, тип результата — после стрелки ->. Значение по умолчанию пишется после аннотации:

def average(values: list[float], digits: int = 2) -> float:
    return round(sum(values) / len(values), digits)

def log(message: str) -> None:   # функция ничего не возвращает
    print(message)

Для *args и **kwargs указывают тип одного элемента: *args: int значит «все позиционные аргументы — числа», **kwargs: str — «все именованные аргументы — строки». Основы объявления функций — в статье о функциях в Python.

Атрибуты классов

Атрибуты экземпляра аннотируют в __init__ или в теле класса. Аннотации в теле класса читает @dataclass и строит по ним конструктор:

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int = 0

print(User("Анна", 30))  # → User(name='Анна', age=30)

Как устроены классы и методы — в статье о классах в Python.

Коллекции: list[int], dict[str, int], tuple

С Python 3.9 встроенные коллекции принимают тип элементов в квадратных скобках, импортировать List и Dict из typing больше не нужно:

scores: dict[str, int] = {"Анна": 5, "Борис": 4}
point: tuple[float, float] = (1.0, 2.5)   # ровно два элемента
row: tuple[int, ...] = (1, 2, 3)          # любое число int
tags: set[str] = {"python", "typing"}
matrix: list[list[int]] = [[1, 2], [3, 4]]

У tuple каждый элемент описывается отдельно, а многоточие ... означает «сколько угодно элементов этого типа». Для словаря первым идёт тип ключа, вторым — тип значения. Методы самих коллекций разобраны в статье о словарях в Python.

Если функция только читает данные, аннотируйте параметр абстрактным типом из collections.abc: Iterable[int] примет и список, и кортеж, и генератор, а Sequence[str] — любую последовательность с индексами. Для результата, наоборот, указывайте конкретный тип: так вызывающий код знает, какие методы ему доступны.

from collections.abc import Callable, Iterable

def apply_all(func: Callable[[int], int], values: Iterable[int]) -> list[int]:
    return [func(v) for v in values]

print(apply_all(lambda x: x * 2, range(4)))  # → [0, 2, 4, 6]

Callable[[int], int] — функция, которая принимает один int и возвращает int. Если параметры неважны, пишут Callable[..., int].

Старый и новый синтаксис

В коде и статьях до 2021 года встречаются импорты из typing. Они по-прежнему работают (на Python 3.15 — без предупреждений), но в новом коде используйте правую колонку:

Было (typing)СталоС версии Python
List[int], Dict[str, int]list[int], dict[str, int]3.9
Tuple[int, str], Set[str]tuple[int, str], set[str]3.9
typing.Callable, typing.Iterablecollections.abc.Callable, collections.abc.Iterable3.9
Union[int, str]int | str3.10
Optional[str]str | None3.10
Vector: TypeAlias = list[float]type Vector = list[float]3.12
T = TypeVar("T") + Generic[T]class Box[T], def first[T]3.12

Несколько типов и None: Union и Optional

Вертикальная черта | объединяет типы: int | str — «число или строка». Самый частый случай — значение, которого может не быть:

def find_user(user_id: int) -> str | None:
    users = {1: "Анна", 2: "Борис"}
    return users.get(user_id)

name = find_user(3)
print(name.upper())

При запуске код упадёт с AttributeError: 'NoneType' object has no attribute 'upper', а mypy найдёт проблему заранее:

main.py:6: error: Item "None" of "str | None" has no attribute "upper"  [union-attr]

После проверки if name is not None: анализатор сужает тип до str, и ошибка исчезает. Optional[str] из typing означает то же, что str | None; запись через | короче и не требует импорта.

Важно: Optional описывает только тип значения, обязательность аргумента задаёт значение по умолчанию. Параметр retries: int = 3 необязателен при вызове, но None в нём недопустим. Если None разрешён, пишите timeout: float | None = None.

Модуль typing: основные конструкции

КонструкцияЧто означаетС версии
Anyлюбой тип, проверка отключается3.5
Literal["r", "w"]только перечисленные значения3.8
Finalконстанта, переприсваивать нельзя3.8
TypedDictсловарь с фиксированными ключами и их типами3.8
Protocolлюбой объект с нужными методами («утиная типизация»)3.8
Annotated[int, ...]тип плюс метаданные для библиотек3.9
NewTypeотдельный тип на основе существующего (UserId из int)3.5
Selfэкземпляр текущего класса3.11
Never, NoReturnфункция никогда не возвращает управление3.11, 3.5
overrideметод обязан переопределять метод родителя3.12
TypeIsфункция-проверка, сужающая тип3.13
TypeFormзначение, которое само является выражением типа3.15

Если нужной конструкции нет в вашей версии Python, её обычно можно импортировать из пакета typing_extensions: он переносит новые объекты typing на старые версии. Новый синтаксис (class Box[T], оператор type) так не перенести — его разбирает парсер.

Any и object

Any совместим с любым типом в обе стороны и отключает проверку: с ним можно вызвать любой метод. object тоже принимает любое значение, но разрешает только то, что есть у всех объектов:

from typing import Any

def log(value: Any) -> None:
    value.anything()    # mypy молчит

def log2(value: object) -> None:
    value.anything()    # error: "object" has no attribute "anything"

Используйте object, когда функции действительно всё равно, что пришло, и Any — как временную заглушку там, где тип пока не описан.

Literal и Final

Literal ограничивает значение конкретным набором, Final запрещает переприсваивание:

from typing import Final, Literal

MAX_RETRIES: Final = 3

def open_file(path: str, mode: Literal["r", "w"] = "r") -> None:
    print(f"Открываю {path} в режиме {mode}")

open_file("data.txt", "x")
MAX_RETRIES = 5
main.py:8: error: Argument 2 to "open_file" has incompatible type "Literal['x']"; expected "Literal['r', 'w']"  [arg-type]
main.py:9: error: Cannot assign to final name "MAX_RETRIES"  [misc]

Код при этом выполнится без ошибок — обе проверки существуют только для анализатора.

TypedDict: словарь с известными ключами

Когда данные приходят как словарь (JSON из API, конфигурация), TypedDict описывает его ключи. В рантайме это обычный dict, никаких накладных расходов:

from typing import NotRequired, TypedDict

class Movie(TypedDict):
    title: str
    year: int
    rating: NotRequired[float]   # ключа может не быть

movie: Movie = {"title": "Солярис", "year": 1972}
print(movie["title"], type(movie).__name__)  # → Солярис dict

bad: Movie = {"title": "Сталкер", "year": "1979"}
main.py:11: error: Incompatible types (expression has type "str", TypedDict item "year" has type "int")  [typeddict-item]

NotRequired (Python 3.11) помечает необязательный ключ. Это отличается от rating: float | None: там ключ обязан быть, но значение может быть None. Если словарь создаёте вы сами, удобнее @dataclass или модель Pydantic — она ещё и проверит данные при выполнении.

Protocol: утиная типизация

Protocol описывает требования к объекту без наследования. Подходит любой класс, у которого есть нужные методы:

from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None: ...

class File:
    def close(self) -> None:
        print("Файл закрыт")

class Connection:
    def close(self) -> None:
        print("Соединение закрыто")

def shutdown(resources: list[SupportsClose]) -> None:
    for resource in resources:
        resource.close()

shutdown([File(), Connection()])
shutdown([42])   # error: List item 0 has incompatible type "int"; expected "SupportsClose"

File и Connection не наследуют SupportsClose, но подходят, потому что у них есть close(). Так типизируют код, который принимает «что-то с методом read()» или «что-то, что можно сравнить».

Self и @override

Self (3.11) означает «экземпляр того класса, у которого вызван метод», в том числе наследника. @override (3.12) просит анализатор убедиться, что метод с таким именем есть у родителя, и ловит опечатки при переименовании:

from typing import Self, override

class Query:
    def __init__(self) -> None:
        self.filters: list[str] = []

    def where(self, condition: str) -> Self:
        self.filters.append(condition)
        return self

class UserQuery(Query):
    def active(self) -> Self:
        return self.where("is_active")

    @override
    def where(self, condition: str) -> Self:
        return super().where(f"user.{condition}")

q = UserQuery().where("age > 18").active()
print(type(q).__name__, q.filters)
# → UserQuery ['user.age > 18', 'user.is_active']

Без Self метод where пришлось бы аннотировать как -> Query, и после UserQuery().where(...) анализатор «забыл» бы про метод active().

Annotated: метаданные для библиотек

Annotated[тип, метаданные] для анализатора равен первому аргументу, а остальное читают библиотеки. На этом построены параметры FastAPI (Annotated[str, Query(max_length=50)]) и ограничения полей Pydantic:

from typing import Annotated, get_type_hints

def set_age(age: Annotated[int, "от 0 до 150"]) -> None: ...

print(get_type_hints(set_age))
# → {'age': <class 'int'>, 'return': <class 'NoneType'>}
print(get_type_hints(set_age, include_extras=True))
# → {'age': typing.Annotated[int, 'от 0 до 150'], 'return': <class 'NoneType'>}

NewType: разные типы на основе одного

NewType создаёт отдельный тип для анализатора, оставляя в рантайме исходный. Так UserId и OrderId нельзя перепутать, хотя оба — int:

from typing import NewType

UserId = NewType("UserId", int)

def get_user(user_id: UserId) -> str:
    return f"user-{user_id}"

print(get_user(UserId(5)))  # → user-5
get_user(5)                 # error: Argument 1 to "get_user" has incompatible type "int"; expected "UserId"

TypeIs: сужение типа своей функцией

isinstance() анализатор понимает сам. Если проверка вынесена в отдельную функцию, укажите результат TypeIs[тип] (3.13), и тип сузится в обеих ветках условия:

from typing import TypeIs

def is_str(value: object) -> TypeIs[str]:
    return isinstance(value, str)

def describe(value: int | str) -> str:
    if is_str(value):
        return value.upper()   # здесь value: str
    return str(value + 1)      # здесь value: int

print(describe("ok"), describe(41))  # → OK 42

Предшественник TypeGuard (3.10) сужает тип только в ветке if. Для проверок вида «это строка?» выбирайте TypeIs.

Псевдонимы типов: оператор type

Длинный тип удобно назвать. С Python 3.12 для этого есть оператор type:

type UserId = int
type Json = dict[str, Json] | list[Json] | str | int | float | bool | None
type Pair[T] = tuple[T, T]

def load(user_id: UserId) -> Json:
    return {"id": user_id, "tags": ["admin"]}

print(load(7))               # → {'id': 7, 'tags': ['admin']}
print(type(Json).__name__)   # → TypeAliasType
print(Json.__value__)
# → dict[str, Json] | list[Json] | str | int | float | bool | None

Правая часть вычисляется лениво, поэтому Json ссылается сам на себя без кавычек. Псевдоним может быть обобщённым (Pair[T]). До 3.12 псевдонимы писали как обычное присваивание Vector = list[float] или с пометкой Vector: TypeAlias = list[float]; TypeAlias объявлен устаревшим, но удалять его не планируют.

Оператор type создаёт объект TypeAliasType, и проверка isinstance({}, Json) выбросит TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union. Если тип нужен для проверок при выполнении, оставьте обычное присваивание.

type[User] и оператор type

Слово type в аннотациях встречается в двух ролях:

  • type X = ... — оператор Python 3.12+, создаёт псевдоним типа;
  • type[User] в аннотации — сам класс User или его наследник: функция получает класс и может создать из него объект.
def make(cls: type[Exception], msg: str) -> Exception:
    return cls(msg)

print(repr(make(ValueError, "плохо")))  # → ValueError('плохо')

Старое написание typing.Type[User] означает то же самое.

Дженерики: TypeVar и синтаксис Python 3.12

Обобщённая функция работает с разными типами, сохраняя связь между ними: first() от списка строк возвращает строку, от списка чисел — число. С 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] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

numbers = Stack[int]()
numbers.push(10)
print(first(["a", "b"]), numbers.pop())  # → a 10
numbers.push("десять")
main.py:17: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"  [arg-type]

Ограничения задаются через двоеточие:

def biggest[T: (int, float)](a: T, b: T) -> T:   # T — только int или float
    return a if a > b else b

def longest[S: str](items: list[S]) -> S:         # S — str или его подкласс
    return max(items, key=len)

print(biggest(3, 7), longest(["кот", "собака"]))  # → 7 собака

С Python 3.13 у параметра типа может быть значение по умолчанию: class Box[T = int]. Голое Box в аннотации тогда означает Box[int].

На Python 3.11 и более ранних версиях новый синтаксис вызывает SyntaxError. Если проект должен работать там, используйте прежнюю запись — mypy понимает обе:

from typing import Generic, TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

Смешивать стили в одном классе нельзя: class A[U](Generic[T]) вызывает TypeError.

Отложенные аннотации в Python 3.14

До Python 3.14 аннотации вычислялись в момент определения функции. Из-за этого метод не мог сослаться на свой класс без кавычек:

class Node:
    def __init__(self, value: int, next: Node | None = None) -> None:
        self.value = value
        self.next = next

    def append(self, value: int) -> Node:
        return Node(value, self)

print(Node(1).append(2).value)

На Python 3.12 этот код падает с NameError: name 'Node' is not defined, и раньше писали "Node" в кавычках или добавляли в начало файла from __future__ import annotations. С Python 3.14 (PEP 649 и PEP 749) аннотации вычисляются лениво — только когда их кто-то читает, — и код выводит 2.

Ошибка с неизвестным именем никуда не исчезает, она переезжает в момент чтения аннотаций. Типичный пример — импорт только для анализатора под if TYPE_CHECKING::

from typing import TYPE_CHECKING, get_type_hints
from annotationlib import Format, get_annotations

if TYPE_CHECKING:
    from decimal import Decimal

def total(price: Decimal, qty: int) -> Decimal:
    return price * qty

print(get_annotations(total, format=Format.STRING))
# → {'price': 'Decimal', 'qty': 'int', 'return': 'Decimal'}
get_type_hints(total)   # NameError: name 'Decimal' is not defined

Новый модуль annotationlib читает аннотации в трёх форматах: VALUE (вычисленные объекты, как раньше), FORWARDREF (неизвестные имена заменяются на ForwardRef) и STRING (исходный текст). Обычному коду это ничего не меняет. Если вы пишете библиотеку, которая читает аннотации, проверьте её на 3.14.

from __future__ import annotations в 3.14 продолжает работать: с ним аннотации сохраняются строками, как в 3.7–3.13. В новых проектах на 3.14+ он не нужен.

Что нового в typing в Python 3.15

Python 3.15 вышел 9 октября 2026 года. Изменения в typing касаются в основном авторов библиотек:

  • TypedDict с closed и extra_items (PEP 728). Закрытый словарь запрещает ключи сверх описанных, extra_items разрешает любые дополнительные ключи заданного типа.
  • TypeForm (PEP 747). Аннотация для значений, которые сами являются типами: int, str | None, list[int]. Пригодится функциям вида parse(int, "42") и валидаторам.
  • @typing.disjoint_base (PEP 800). Помечает класс, с которым нельзя множественно наследоваться вместе с другими такими же классами.
  • Встроенный тип sentinel (PEP 661). Уникальные значения-маркеры вроде MISSING, которые можно использовать в аннотациях через |.
from typing import TypedDict

class Point(TypedDict, closed=True):
    x: int
    y: int

class Headers(TypedDict, extra_items=str):
    host: str

h: Headers = {"host": "example.com", "accept": "text/html"}
print(Point.__closed__, Headers.__extra_items__)  # → True <class 'str'>

Удалены давно устаревшие вещи: декоратор @typing.no_type_check_decorator, создание NamedTuple через именованные аргументы (NamedTuple("Point", x=int)) и пустой TypedDict("TD"). typing.ByteString убран из typing.__all__ и будет удалён в 3.17 — вместо него используйте collections.abc.Buffer или bytes | bytearray. Поддержка новых возможностей в анализаторах появляется с их обновлениями, поэтому перед использованием проверьте свою версию mypy или pyright.

Как проверить типы: mypy, pyright, ty

Аннотации полезны, когда их кто-то проверяет. Основные анализаторы:

ИнструментКак установитьОсобенности
mypypip install mypyэталонный анализатор, проверяет только аннотированные функции по умолчанию
pyrightрасширение Pylance в VS Code или pip install pyrightбыстрый, работает прямо в редакторе
typip install tyанализатор от Astral на Rust, в версиях 0.0.x

Установите mypy в виртуальное окружение проекта и запустите на файле или папке:

py -m pip install mypy
py -m mypy main.py
python3 -m pip install mypy
python3 -m mypy main.py

Чтобы узнать, какой тип анализатор вывел для выражения, используйте reveal_type(). С 3.11 её можно импортировать из typing, тогда код работает и при запуске:

from typing import reveal_type

def first[T](items: list[T]) -> T:
    return items[0]

reveal_type(first([1.5, 2.5]))
$ mypy main.py
main.py:6: note: Revealed type is "float"

$ python main.py
Runtime type is 'float'

По умолчанию mypy пропускает тела функций без аннотаций. Флаг --strict включает строгий режим: требует аннотации везде и запрещает вызывать неаннотированные функции из типизированного кода. Для старого проекта начните с обычного режима и включайте строгость по модулям. Для сторонних библиотек без встроенных аннотаций ставят пакеты-заглушки: pip install types-requests, types-PyYAML и т. п.

Аннотации во время выполнения

Python сохраняет аннотации, но не применяет их. Прочитать их можно через get_type_hints() или __annotations__:

def total(price: float, qty: int) -> float:
    return price * qty

print(total.__annotations__)
# → {'price': <class 'float'>, 'qty': <class 'int'>, 'return': <class 'float'>}

Проверить значение на обобщённый тип через isinstance() нельзя: isinstance([1, 2], list[int]) выбросит TypeError: isinstance() argument 2 cannot be a parameterized generic. Проверяйте основной тип (isinstance(x, list)), а для настоящей проверки данных по аннотациям используйте Pydantic.

Частые вопросы

Ускоряют или замедляют аннотации программу?

На скорость выполнения в CPython они не влияют: интерпретатор их не использует. В Python 3.14 аннотации ещё и не вычисляются при определении функции, поэтому их стоимость при импорте модуля стала минимальной. Ускорение дают отдельные инструменты, например компилятор mypyc, который превращает аннотированный код в расширение на C.

Нужно ли импортировать List, Dict и Optional из typing?

Нет, если проект работает на Python 3.10 и новее: пишите list[int], dict[str, int] и int | None. List и Dict устарели ещё в 3.9, Optional и Union формально не устарели, но запись через | короче. Все эти имена работают и в 3.15, а линтер Ruff умеет автоматически переписать их на новый синтаксис.

Как аннотировать функцию, которая ничего не возвращает?

-> None — для функции, которая завершается и возвращает None (неявно или через голый return). -> Never (или NoReturn) — для функции, которая никогда не возвращает управление: всегда выбрасывает исключение или вызывает sys.exit().

Аннотации загромождают код. Можно вынести их отдельно?

Да, в stub-файлы с расширением .pyi: рядом с module.py кладут module.pyi с сигнатурами без тел функций, и анализатор берёт типы оттуда. Так типизируют библиотеки на C и чужой код. Для своего кода аннотации в сигнатурах обычно удобнее: их видно при чтении функции, и они не расходятся с реализацией.

Чем TypedDict отличается от dataclass?

TypedDict описывает обычный словарь и существует только для анализатора: удобен для JSON и данных, которые уже приходят словарями. @dataclass создаёт класс с атрибутами, конструктором и сравнением — для объектов, которые вы создаёте сами. Ни один из них не проверяет типы при выполнении.

Какой анализатор выбрать?

Для начала хватит редактора: Pylance в VS Code и PyCharm подсвечивают ошибки типов без настройки. Для проверки в CI (непрерывной интеграции) чаще всего берут mypy — он стабилен и лучше всех задокументирован. pyright и ty работают быстрее на больших проектах; результаты разных анализаторов иногда расходятся в спорных случаях.

Итог

Аннотации типов описывают, какие значения ожидает код: x: int, -> str, list[str], int | None. Python их не проверяет — это делают mypy, pyright и редактор, а библиотеки вроде Pydantic и FastAPI превращают аннотации в проверку данных. Модуль typing нужен для сложных случаев: TypedDict для словарей, Protocol для утиной типизации, Literal, Final, Self, Annotated. В современном Python дженерики пишутся как class Stack[T], псевдонимы — оператором type (3.12), кавычки вокруг ссылок на ещё не объявленные классы не нужны (3.14), а 3.15 добавил закрытые TypedDict и TypeForm. Начните с аннотаций в сигнатурах публичных функций и запускайте mypy — первые найденные ошибки покажут, где типы окупаются.

  • #Python
  • #typing
  • #Аннотации типов
  • #mypy
  • #Синтаксис
↑ ↓ выборEnter открытьEsc закрыть