EasyТеория5 min

Основы аннотаций типов

Базовые аннотации типов: int, str, list, dict, Optional, Union и синтаксис X | Y

Аннотации типов (type hints) появились в Python 3.5 и с каждой версией становятся все мощнее. Они не влияют на выполнение программы, но помогают инструментам статического анализа находить ошибки, а разработчикам -- читать код. В Python 3.14 система типов достигла зрелости.

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

# Without type hints — what types are expected?
def process(data, threshold):
    return [x for x in data if x > threshold]

# With type hints — instantly clear
def process(data: list[float], threshold: float) -> list[float]:
    return [x for x in data if x > threshold]

Преимущества:

  • IDE подсказывает методы и ловит ошибки
  • Статический анализатор (mypy, pyright) находит баги до запуска
  • Код служит документацией
  • Рефакторинг безопаснее

Базовые типы

# Primitive types
name: str = "Alice"
age: int = 30
height: float = 1.75
is_active: bool = True
data: bytes = b"hello"

# None type
result: None = None

# Function annotations
def greet(name: str) -> str:
    """Return a greeting message."""
    return f"Hello, {name}!"

def save_to_db(user: dict) -> None:
    """Save user to database. Returns nothing."""
    pass

# Variables without initial value
user_id: int  # Valid — declares type without assignment

Коллекции

Начиная с Python 3.9, встроенные типы коллекций можно использовать напрямую в аннотациях:

# Python 3.9+ — use built-in types directly
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95, "Bob": 87}
unique_ids: set[int] = {1, 2, 3}
coordinates: tuple[float, float] = (55.75, 37.62)
frozen: frozenset[str] = frozenset({"a", "b"})

# Nested collections
matrix: list[list[int]] = [[1, 2], [3, 4]]
config: dict[str, list[str]] = {
    "allowed_hosts": ["localhost", "example.com"],
    "admins": ["alice", "bob"],
}

# Tuple with variable length
tags: tuple[str, ...] = ("python", "typing", "tutorial")

# Fixed-length tuple (each position has its own type)
record: tuple[str, int, bool] = ("Alice", 30, True)

Python 3.8 и ниже -- используйте typing

# Before Python 3.9 — import from typing
from typing import List, Dict, Set, Tuple, FrozenSet

names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 95}
ids: Set[int] = {1, 2, 3}
point: Tuple[float, float] = (1.0, 2.0)

Union и Optional

Оператор | (Python 3.10+)

# Python 3.10+ — pipe syntax for unions
def parse_id(value: str | int) -> int:
    """Accept string or int, return int."""
    if isinstance(value, str):
        return int(value)
    return value

# Multiple types
def process(value: str | int | float | None) -> str:
    if value is None:
        return "N/A"
    return str(value)

Optional -- сокращение для X | None

from typing import Optional

# These are equivalent:
name: str | None = None
name: Optional[str] = None

# Function that may return None
def find_user(user_id: int) -> dict | None:
    """Find user by ID. Returns None if not found."""
    users = {1: {"name": "Alice"}}
    return users.get(user_id)

# Default values
def greet(name: str, title: str | None = None) -> str:
    """Greet with optional title."""
    if title:
        return f"Hello, {title} {name}!"
    return f"Hello, {name}!"

Any -- отключение проверки типов

from typing import Any

# Any matches any type — use sparingly
def log_value(value: Any) -> None:
    """Log any value."""
    print(f"Value: {value}")

# Any is compatible with everything
x: Any = 42
y: str = x  # No error from type checker
z: Any = "hello"

# Prefer specific types over Any
# BAD: def process(data: Any) -> Any
# GOOD: def process(data: list[str]) -> dict[str, int]

Аннотации функций

# Parameters and return type
def add(a: int, b: int) -> int:
    return a + b

# *args and **kwargs
def log(*messages: str, level: str = "INFO") -> None:
    for msg in messages:
        print(f"[{level}] {msg}")

def create_user(**kwargs: str | int) -> dict[str, str | int]:
    return dict(kwargs)

# Callable — function as parameter
from collections.abc import Callable

def apply_operation(
    values: list[int],
    operation: Callable[[int], int]
) -> list[int]:
    """Apply a function to each value."""
    return [operation(v) for v in values]

result = apply_operation([1, 2, 3], lambda x: x ** 2)
print(result)  # [1, 4, 9]

Аннотации переменных класса

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    email: str
    age: int
    tags: list[str] = field(default_factory=list)
    is_active: bool = True

    def full_info(self) -> str:
        """Return formatted user info."""
        return f"{self.name} ({self.email}), age {self.age}"

class Config:
    """Application configuration."""

    # Class variable annotations
    debug: bool = False
    max_connections: int = 100
    allowed_hosts: list[str] = []

    def __init__(self, env: str = "production") -> None:
        self.env: str = env  # Instance variable annotation
        self.port: int = 8080

type alias -- псевдонимы типов

# Simple type alias
UserId = int
Username = str
Headers = dict[str, str]

def get_user(user_id: UserId) -> Username:
    users: dict[UserId, Username] = {1: "Alice", 2: "Bob"}
    return users.get(user_id, "Unknown")

# Complex alias
JSON = dict[str, "JSON | str | int | float | bool | list | None"]

# type statement (Python 3.12+)
type Vector = list[float]
type Matrix = list[Vector]
type UserMap = dict[int, User]

def dot_product(a: Vector, b: Vector) -> float:
    return sum(x * y for x, y in zip(a, b))

Аннотации для коллекций из collections.abc

Для более общих типов используйте абстрактные классы:

from collections.abc import Sequence, Mapping, Iterable, Iterator

# Accept any sequence (list, tuple, etc.)
def first_element(items: Sequence[int]) -> int | None:
    return items[0] if items else None

# Accept any mapping (dict, OrderedDict, etc.)
def get_keys(data: Mapping[str, int]) -> list[str]:
    return list(data.keys())

# Accept any iterable
def process_items(items: Iterable[str]) -> list[str]:
    return [item.upper() for item in items]

# Return an iterator
def generate_ids(start: int = 1) -> Iterator[int]:
    while True:
        yield start
        start += 1

Практический пример: типизированный API-клиент

from dataclasses import dataclass
from collections.abc import Callable

@dataclass
class APIResponse:
    status: int
    data: dict[str, str | int | list | None]
    headers: dict[str, str]

    @property
    def is_success(self) -> bool:
        return 200 <= self.status < 300

def make_request(
    url: str,
    method: str = "GET",
    params: dict[str, str] | None = None,
    headers: dict[str, str] | None = None,
    timeout: float = 30.0,
    on_error: Callable[[int, str], None] | None = None,
) -> APIResponse:
    """Make an HTTP request with full type safety."""
    # Simulated response
    response = APIResponse(
        status=200,
        data={"message": "success"},
        headers={"content-type": "application/json"},
    )

    if not response.is_success and on_error:
        on_error(response.status, str(response.data))

    return response

# Usage — IDE knows all types
resp = make_request(
    "https://api.example.com/users",
    params={"page": "1"},
    timeout=10.0,
)
print(resp.data)  # IDE: dict[str, str | int | list | None]

Проверь себя

Чем Any отличается от object?

Влияют ли аннотации типов на выполнение программы в Python?

Какой модуль нужно использовать для Sequence, Mapping и других абстрактных коллекций?

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

Как с Python 3.10 записать, что переменная может быть str или None?