Аннотации типов в Python — полный гайд с примерами
-
Python динамически типизирован: интерпретатор не проверяет типы во время выполнения. Аннотации типов — это подсказки (type hints) для инструментов статического анализа, IDE и разработчиков.
Три главных эффекта:
- Читаемость — сразу видно, что принимает и возвращает функция
- Статический анализ — mypy, pyright находят ошибки до запуска
- Автодополнение в IDE — Zed, VS Code, PyCharm знают типы и предлагают правильные методы
Аннотации не влияют на скорость выполнения и не бросают исключений сами по себе. Проверку можно включить явно через runtime-библиотеки.
Базовый синтаксис
Переменные
name: str = "Alice" age: int = 30 pi: float = 3.14 is_active: bool = True nothing: None = None # Объявление без инициализации (для классов, модульного уровня) user_id: int # значения ещё нет, но тип зафиксированФункции
def greet(name: str) -> str: return f"Hello, {name}" def add(a: int, b: int) -> int: return a + b def log(message: str) -> None: # ничего не возвращает print(message)Синтаксис: после имени параметра ставится
: тип, после скобок-> типдля возвращаемого значения.
Встроенные коллекции (Python 3.9+)
С Python 3.9 можно использовать встроенные типы напрямую, без импорта из
typing:def process(items: list[int]) -> dict[str, int]: return {str(i): i for i in items} def lookup(mapping: dict[str, list[float]]) -> tuple[str, ...]: return tuple(mapping.keys()) coords: set[int] = {1, 2, 3} pair: tuple[int, str] = (42, "hello")До Python 3.9 нужно было писать
List[int],Dict[str, int]из модуляtyping.
Модуль
typing— специальные конструкцииOptional— значение илиNonefrom typing import Optional def find_user(user_id: int) -> Optional[str]: if user_id == 1: return "Alice" return NoneС Python 3.10 то же самое можно записать через
|:def find_user(user_id: int) -> str | None: # Python 3.10+ ...Union— один из нескольких типовfrom typing import Union def stringify(value: Union[int, float, str]) -> str: return str(value) # Python 3.10+ def stringify(value: int | float | str) -> str: return str(value)Any— отключение проверкиfrom typing import Any def debug(value: Any) -> None: print(value)Anyсовместим с любым типом в обе стороны. Используется, когда тип действительно неизвестен или для постепенной миграции.Literal— конкретные допустимые значенияfrom typing import Literal def set_direction(direction: Literal["left", "right", "up", "down"]) -> None: ... def get_status() -> Literal[0, 1, -1]: return 0Final— константыfrom typing import Final MAX_SIZE: Final = 100 API_URL: Final[str] = "https://api.example.com"Переменная, помеченная
Final, не должна переопределяться.
Callable и функции как аргументы
from typing import Callable # Функция принимает int, возвращает str def apply(func: Callable[[int], str], value: int) -> str: return func(value) # Функция без аргументов, возвращающая None Handler = Callable[[], None] def register(callback: Handler) -> None: callback()
TypeVar — обобщённые функции
TypeVarнужен, когда тип возврата зависит от типа аргумента:from typing import TypeVar T = TypeVar("T") def first(items: list[T]) -> T: return items[^0] result_int: int = first([1, 2, 3]) # T = int result_str: str = first(["a", "b"]) # T = strTypeVarс ограничениями:from typing import TypeVar Number = TypeVar("Number", int, float) def double(x: Number) -> Number: return x * 2Python 3.12 — новый синтаксис дженериков
# Python 3.12+, не нужно объявлять TypeVar явно def first[T](items: list[T]) -> T: return items[^0] def zip_with[T, U](a: list[T], b: list[U]) -> list[tuple[T, U]]: return list(zip(a, b))
Аннотации в классах
class User: name: str age: int email: str | None = None # поле со значением по умолчанию def __init__(self, name: str, age: int) -> None: self.name = name self.age = age def greet(self) -> str: return f"Hi, I'm {self.name}"ClassVar— атрибуты класса, не экземпляраfrom typing import ClassVar class Config: debug: ClassVar[bool] = False max_retries: ClassVar[int] = 3 timeout: float = 30.0 # атрибут экземпляра
TypedDict — словари с фиксированной структурой
from typing import TypedDict class Movie(TypedDict): title: str year: int rating: float def print_movie(movie: Movie) -> None: print(f"{movie['title']} ({movie['year']})") film: Movie = {"title": "Inception", "year": 2010, "rating": 8.8}С необязательными ключами:
from typing import TypedDict, NotRequired class Config(TypedDict): host: str port: int debug: NotRequired[bool] # Python 3.11+
Protocol — структурная типизация (duck typing)
Protocolпозволяет описать интерфейс, которому объект должен соответствовать, без наследования:from typing import Protocol class Drawable(Protocol): def draw(self) -> None: ... class Circle: def draw(self) -> None: print("Drawing circle") class Square: def draw(self) -> None: print("Drawing square") def render(shape: Drawable) -> None: shape.draw() render(Circle()) # OK, хотя Circle не наследует Drawable render(Square()) # OK
dataclass+ аннотацииfrom dataclasses import dataclass, field @dataclass class Point: x: float y: float label: str = "default" tags: list[str] = field(default_factory=list) p = Point(1.0, 2.5)@dataclassчитает аннотации классовых полей и автоматически генерирует__init__,__repr__,__eq__.
overload— перегрузка сигнатурfrom typing import overload @overload def process(value: int) -> str: ... @overload def process(value: str) -> int: ... def process(value: int | str) -> str | int: if isinstance(value, int): return str(value) return len(value)@overloadпозволяет статическому анализатору понять, какой тип вернётся при каком входе.
TYPE_CHECKING— избегаем циклических импортовfrom __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from mymodule import HeavyClass # импортируется только при статическом анализе def process(obj: "HeavyClass") -> None: ...from __future__ import annotations(PEP 563) делает все аннотации строками-ленивыми вычислениями, что полностью решает проблему forward references.
ParamSpecиConcatenate— аннотации для декораторовfrom typing import ParamSpec, TypeVar, Callable import functools P = ParamSpec("P") R = TypeVar("R") def logged(func: Callable[P, R]) -> Callable[P, R]: @functools.wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper @logged def add(a: int, b: int) -> int: return a + bParamSpecсохраняет полную сигнатуру оборачиваемой функции — IDE будет знать типы аргументов декорированной функции.
Self— тип текущего классаfrom typing import Self # Python 3.11+ class Builder: def set_name(self, name: str) -> Self: self.name = name return self def set_age(self, age: int) -> Self: self.age = age return self b = Builder().set_name("Alice").set_age(30)Используйте
Selfвместо имени класса в строке, чтобы наследники получали правильный тип при цепочечных вызовах.
NeverиNoReturnfrom typing import NoReturn, Never # Never появился в 3.11 def crash() -> NoReturn: raise RuntimeError("Fatal error") def assert_never(value: Never) -> Never: raise AssertionError(f"Unexpected value: {value}")NoReturnговорит: функция никогда не возвращает управление (бросает исключение или бесконечный цикл).Never— тип-дно, с которым ничего не совместимо.
Инструменты статического анализа
Инструмент Установка Запуск mypy uv add mypymypy src/pyright uv add pyrightpyrightruff uv add ruffruff check .(lint + types)Пример конфига
mypy.ini:[mypy] python_version = 3.14 strict = true ignore_missing_imports = trueФлаг
--strictвключает полный набор проверок, включаяdisallow_untyped_defs,warn_return_anyи другие.
Быстрая шпаргалка по версиям
Конструкция Минимальная версия list[int],dict[str, int]напрямую3.9 X \| YвместоUnion[X, Y]3.10 Self,Never,NotRequired3.11 def f[T](...)— новый синтаксис дженериков3.12
Полезные источники
- PEP 484 — основной PEP, вводящий аннотации типов: python.org/dev/peps/pep-0484
- PEP 526 — аннотации переменных: python.org/dev/peps/pep-0526
- PEP 604 — синтаксис
X | Y: python.org/dev/peps/pep-0604 - PEP 695 — дженерики через
[T]в Python 3.12: python.org/dev/peps/pep-0695 - Документация mypy: mypy.readthedocs.io
- typing — официальная документация: docs.python.org/3/library/typing.html
- Pyright: github.com/microsoft/pyright
⁂
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.