Обычно знакомство с аннотациями начинается случайно. Открываешь чужой код и видишь такое:
def load_user(user_id: int): ...
Первое ощущение простое: читать стало легче. Не потому что код стал умнее, а потому что вопросов стало меньше. Какой тип у user_id, уже понятно. Не нужно лезть внутрь функции.
Если добавить возвращаемое значение, картина становится ещё яснее.
def load_user(user_id: int) -> dict: ...
Это всё ещё Python. Код выполняется точно так же. Аннотация ничего не проверяет. Но как читатель ты уже понимаешь, чего ожидать на выходе.
Когда без аннотаций становится неудобно
Рассмотрим обычную функцию из продакшена.
def calculate_total(data): return data["price"] * data["count"]
Функция короткая, логика понятная. Пока data всегда приходит в нужном формате, проблем нет.
Но стоит где-то передать данные из другого источника:
calculate_total({"price": "10", "count": 3})
Ошибка не возникает. Результат странный. И Python здесь абсолютно прав.
Добавим аннотацию.
def calculate_total(data: dict[str, int]) -> int: return data["price"] * data["count"]
Теперь хотя бы видно, что строка в price сюда не вписывается. Даже если интерпретатор промолчит, человек и инструменты анализа уже нет.
typing как способ объяснять намерения
Иногда тип list или dict слишком грубый. Важно не то, что это за контейнер, а что в нём лежит.
from typing import List def average(values: List[int]) -> float: return sum(values) / len(values)
Здесь ясно, что ожидаются числа. Не строки, не объекты, не что угодно.
Если контейнер не принципиален, можно выразить это прямо.
from typing import Iterable def log_ids(ids: Iterable[int]) -> None: for user_id in ids: print(user_id)
Функции всё равно, список это, кортеж или результат генератора. Это видно сразу, без чтения т��ла.
Optional и честность в интерфейсах
Одна из самых полезных вещей в typing — возможность явно показать, что значение может отсутствовать.
from typing import Optional def find_user(user_id: int) -> Optional[dict]: if user_id not in database: return None return database[user_id]
Без аннотации нужно либо читать код, либо гадать. С ней сразу ясно, что None — нормальный сценарий, а не ошибка.
И это влияет на использование.
user = find_user(42) if user is not None: send_email(user)
Аннотация здесь задаёт правильный ритм мышления.
Сложные данные без комментариев
Часто встречается код, где структура данных описана текстом.
# data: список словарей с ключами id, name, is_active def process(data): ...
С typing это можно перенести ближе к коду.
from typing import TypedDict, List class UserData(TypedDict): id: int name: str is_active: bool def process(data: List[UserData]) -> None: ...
Ничего принципиально не изменилось. Но структура данных перестала быть неявной.
Что в итоге
Аннотации и модуль typing не делают Python строгим. Они делают код объяснимым.
Если функция маленькая и живёт неделю, они не нужны. Если код живёт годами и через него проходят несколько человек, без них становится тяжело.
Лучший подход обычно очень приземлённый:
добавлять аннотации там, где без них приходится угадывать.
И если код стало проще читать, значит ты всё сделал правильно.
Анонсы новых статей, полезные материалы, мемы и различные фичи в IT, а так же если у вас есть вопросы по статье, вы можете задать из в моем Telegram-сообществе.
