Аннотации типов (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]