Аннотация типов функций в языке Python

Раздел: Основы 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 * times

Python 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']"

Аннотация типа функции в Python - comments

En
Python typing function (python)