Аннотация типов функций в языке Python
Основы аннотации функций в Python
Аннотации типов (type hints) позволяют указать ожидаемые типы параметров и возвращаемого значения функции. Это улучшает читаемость кода, помогает инструментам статического анализа (mypy, Pyright) и IDE (PyCharm, VS Code) обнаруживать потенциальные ошибки. Однако аннотации не влияют на выполнение программы - они игнорируются интерпретатором.
Как правильно указать типы параметров и результат функции?
Основной способ - добавить двоеточие после имени параметра с указанием типа, а после стрелки -> написать тип возвращаемого значения.
def greet(name: str, age: int) -> str:
return f"Меня зовут {name}, мне {age} лет."Python type self (тип self в python)
Для параметров со значением по умолчанию тип указывается перед знаком равенства:
def repeat(text: str, times: int = 1) -> str:
return text * timesPython typing function (аннотация типа функции в python)
Если функция ничего не возвращает, используется None:
def log(message: str) -> None:
print(message)Python 3.12 type (новые возможности типов в python 3.12)
Типичные ошибки:
- Забыть указать тип возвращаемого значения - mypy выдаст предупреждение.
- Указать тип, не импортировав его (например,
Listизtypingдо Python 3.9). - Использовать неподдерживаемые аннотации в старых версиях Python (до 3.5). Решение - установить
from __future__ import annotationsили использовать строковые аннотации.
Как аннотировать функцию, принимающую список чисел и возвращающую словарь?
Используются обобщённые типы из модуля typing (в Python 3.9+ можно использовать встроенные list, dict с квадратными скобками):
from typing import List, Dict
def process_numbers(numbers: List[int]) -> Dict[str, int]:
return {"sum": sum(numbers), "count": len(numbers)}Python typing type checking (проверка типов с помощью typing в python)
В Python 3.9+ запись короче:
def process_numbers(numbers: list[int]) -> dict[str, int]:
return {"sum": sum(numbers), "count": len(numbers)}Python return type (тип возврата функции в python)
Проблема: если список может содержать разные типы (например, int и float), стоит указать Union[int, float] или общий тип float, так как int является подтипом float в Python с точки зрения типизации.
Как указать, что параметр может быть None?
Используется Optional[T], что эквивалентно Union[T, None].
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
if user_id in database:
return database[user_id]
return NoneМожно также писать Union[str, None] или в Python 3.10+ - str | None.
Ошибка: Optional[str] не означает “необязательный аргумент”, а лишь “может быть None”. Для необязательных параметров со значением по умолчанию используется = None. Пример правильного сочетания:
def configure(timeout: Optional[int] = None) -> None:Если параметр обязателен, но может быть None, пишите arg: Optional[str] (без = None).
Как передать функцию как аргумент?
Тип вызываемого объекта описывается с помощью Callable[[аргументы], возврат]. Пустые скобки - без параметров.
from typing import Callable
def apply(func: Callable[[int, int], int], x: int, y: int) -> int:
return func(x, y)Если сигнатура не важна, можно использовать Callable[..., Any].
Проблема: mypy может не проверять точное количество аргументов, если указан Callable[[...], T].
Как создать обобщённую функцию с TypeVar?
TypeVar позволяет параметризовать функцию по типу, сохраняя связь между аргументами и результатом.
from typing import TypeVar
T = TypeVar('T')
def first(elements: list[T]) -> T:
return elements[0]Можно ограничить допустимые типы, передав кортеж:
T = TypeVar('T', int, float)
def add(a: T, b: T) -> T:
return a + bОшибка: попытка использовать TypeVar без привязки к типам в теле функции может вызвать ошибку mypy. Например, если функция ожидает универсальный тип, но внутри вызывается метод, отсутствующий у всех возможных типов.
Как использовать перегрузку функций?
Перегрузка (overload) позволяет описать несколько сигнатур для одной функции, при этом реализация - одна. Используется декоратор @overload.
from typing import overload, Union
@overload
def process(data: int) -> str: ...
@overload
def process(data: str) -> int: ...
def process(data: Union[int, str]) -> Union[int, str]:
if isinstance(data, int):
return str(data)
else:
return int(data)Перегрузки пишутся без тела, с троеточием. Реализация должна быть самой общей.
Частая ошибка: не включить все перегрузки или не предоставить общую реализацию. mypy выдаст ошибку, если реализация не покрывает все варианты.
Как аннотировать корутину или генератор?
Для асинхронных функций используется Coroutine[YieldType, SendType, ReturnType] или AsyncGenerator. Для генераторов - Generator[YieldType, SendType, ReturnType].
from typing import Generator, AsyncGenerator
def count_up_to(n: int) -> Generator[int, None, None]:
for i in range(n):
yield i
async def async_range(n: int) -> AsyncGenerator[int, None]:
for i in range(n):
await asyncio.sleep(0.1)
yield iЕсли корутина не принимает send и не возвращает значения, часто используется AsyncIterator из collections.abc.
Проблема: путаница между Generator и Iterator. Для простой итерации достаточно Iterator[T].
Расширенные примеры и результаты
Пример 1. Проверка аннотаций с mypy. Код с ошибкой типа:
def wrong_add(a: int, b: int) -> int:
return a + b + "0" # Ошибка: str + int$ mypy script.py
script.py:2: error: Unsupported operand types for + ("int" and "str")Пример 2. Использование Protocol для структурной типизации (утиная типизация):
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
def render(obj: Drawable) -> None:
obj.draw()
class Circle:
def draw(self) -> None:
print("Окружность")
render(Circle()) # OKОкружность
Пример 3. Параметризованный класс с Generic и методом:
from typing import Generic, TypeVar, List
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
stack_int = Stack[int]()
stack_int.push(1)
value = stack_int.pop() # value имеет тип int(без вывода, но mypy подтверждает корректность)
Пример 4. Аннотации для декоратора с сохранением сигнатуры (ParamSpec и Concatenate):
from typing import Callable, ParamSpec, TypeVar, Concatenate
P = ParamSpec('P')
R = TypeVar('R')
def log_call(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"Вызов {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def summa(a: int, b: int) -> int:
return a + b
result = summa(2, 3)
print(result)Вызов summa 5
Пример 5. Использование Literal для точных значений:
from typing import Literal
def set_mode(mode: Literal['auto', 'manual']) -> None:
pass
set_mode('auto') # OK
set_mode('test') # Ошибка mypy$ mypy example.py error: Argument 1 to "set_mode" has incompatible type "Literal['test']"; expected "Literal['auto', 'manual']"