Аннотации типов в Python
В больших проектах бывает трудно с первого взгляда понять, какие именно данные принимает функция или что хранится в переменной. Чтобы сделать код более предсказуемым и безопасным, в Python добавили аннотации типов.
Сравните два варианта одной и той же функции:
Python 3.13# Было: что такое user? Объект? Данные из базы? Что вернет функция? def get_discount(user): pass # Стало: сразу понятно, что передаем ID (число), а получаем скидку (число) def get_discount(user_id: int) -> float: pass
Аннотации помогают разработчикам и IDE (среде разработки) точно понимать, какие типы данных ожидаются в разных частях программы, что значительно ускоряет написание и отладку кода.
Что такое аннотации типов?
Аннотации типов — это специальный синтаксис, который позволяет явно указывать ожидаемые типы данных для переменных, аргументов функций и возвращаемых значений.
Важно понимать: Python остаётся языком с динамической типизацией. Аннотации типов — это лишь подсказки. Сам Python не остановит выполнение программы, если вы передадите строку вместо числа. Однако они незаменимы для:
- Улучшения автодополнения кода в IDE (PyCharm, VS Code)
- Статического анализа кода (например, линтером mypy)
- Самодокументируемости кода (код проще читать и понимать)
Базовый синтаксис
Аннотация переменных
Для аннотации переменных ставится двоеточие : после имени переменной, а затем указывается тип:
Python 3.13# Аннотация базовых типов >>> name: str = "Алексей" >>> age: int = 28 >>> height: float = 1.82 >>> is_developer: bool = True
Аннотация функций
В функциях мы можем указать типы для передаваемых аргументов и тип возвращаемого значения (используя стрелку ->):
Python 3.13>>> def greet(name: str, age: int) -> str: ... return f"Привет, {name}! Тебе {age} лет." >>> message = greet("Иван", 25) >>> print(message)Привет, Иван! Тебе 25 лет.
В этом примере:
- name: str — ожидается строка
- age: int — ожидается целое число
- -> str — функция обязуется вернуть строку
(Примечание: если функция ничего не возвращает, мы используем тип возвращаемого значения None, например -> None).
Типизация коллекций (списки, словари)
Вы можете использовать стандартные коллекции для аннотации их содержимого:
Python 3.13# Список целых чисел >>> numbers: list[int] = [1, 2, 3, 4, 5] # Словарь, где ключи - строки, а значения - числа >>> user_ages: dict[str, int] = { ... "Иван": 25, ... "Анна": 22 ... } # Кортеж с четкой структурой: (строка, целое число, число с точкой) >>> user_info: tuple[str, int, float] = ("Алексей", 30, 75.5) # Множество строк >>> unique_names: set[str] = {"Иван", "Анна", "Петр"}
Модуль typing
Для более сложных сценариев используется встроенный модуль typing:
Optional (Опциональное значение)
Если переменная может содержать значение определенного типа или None, используйте Optional:
Python 3.13>>> from typing import Optional >>> def get_user_email(user_id: int) -> Optional[str]: ... if user_id == 1: ... return "admin@example.com" ... return None # Возвращаем None, если пользователь не найден
Union (Объединение типов)
Когда переменная может принимать один из нескольких возможных типов:
Python 3.13>>> from typing import Union # Функция может принимать как целое число, так и число с точкой >>> def process_price(price: Union[int, float]) -> float: ... return float(price) * 1.2 # Добавляем 20% НДС
Callable (Функции как аргументы)
Если вы передаете функцию как аргумент в другую функцию, её тоже можно типизировать.
Callable принимает два аргумента: список типов входных параметров и тип возвращаемого значения:
Python 3.13>>> from typing import Callable >>> def apply_twice(value: int, func: Callable[[int], int]) -> int: ... return func(func(value)) >>> def double(x: int) -> int: ... return x * 2 >>> result = apply_twice(3, double) # double(double(3)) = 12
Создание собственных типов
Чтобы не писать длинные сложные типы по много раз, вы можете создавать псевдонимы типов:
Python 3.13# Создаем псевдоним >>> Coordinates = tuple[float, float] >>> UserDict = dict[str, str | int] >>> def get_location() -> Coordinates: ... return (55.7558, 37.6173) >>> def process_user(user: UserDict) -> None: ... pass
Зачем всё это нужно?
- Меньше багов: Ваш редактор кода подчеркнет ошибку красным еще до запуска программы, если вы попытаетесь передать строку "" туда, где ожидается int.
- Идеальное автодополнение: IDE будет точно знать, какие методы есть у объекта, потому что вы указали его тип.
- Легче читать код: Читая заголовок def get_user(user_id: int) -> dict[str, str]:, вы мгновенно понимаете, что функция принимает числовой ID и возвращает словарь. Не нужно вчитываться в тело функции.
Проверка понимания
Давайте проверим ваши знания об аннотациях типов:
Как правильно описать функцию, которая принимает целое число и опциональную строку (может быть None), а возвращает список чисел?
