Тип возвращаемого значения функции: инструкция по аннотациям

Раздел: Основы Python -> Типы данных и аннотации

Основные способы указания типа возврата

Наиболее универсальный и эффективный подход - использовать синтаксис аннотации со стрелкой -> и именем типа. Этот способ работает во всех современных версиях Python (3.5+), поддерживается статическими анализаторами (mypy, pyright) и делает код более предсказуемым и документированным.

def get_age() -> int:
    return 30

Python type self (тип self в python)

Если функция ничего не возвращает, указывается None:

def log_message(msg: str) -> None:
    print(msg)

Python typing function (аннотация типа функции в python)

Типичная ошибка: забыть возвращаемое значение в теле функции. Если аннотировать -> int, а return отсутствует, функция вернёт None, и анализатор выдаст предупреждение. Решение: явно ставить -> None для процедур и проверять наличие return в ветках кода.

Как обозначить, что функция не возвращает значение?

Указывается тип None после стрелки. Это явно говорит о том, что функция выполняется ради побочных эффектов.

def write_file(path: str, data: str) -> None:
    with open(path, 'w') as f:
        f.write(data)

Python 3.12 type (новые возможности типов в python 3.12)

Проблема: если в теле функции присутствует return some_value, mypy сообщит об ошибке несоответствия типов. Нужно следить, чтобы все return возвращали None или отсутствовали.

Как аннотировать функцию, возвращающую один из нескольких типов (например, число или строку)?

Используется Union из модуля typing или оператор | в Python 3.10+.

from typing import Union

def parse_number(text: str) -> Union[int, float]:
    try:
        return int(text)
    except ValueError:
        return float(text)

Python typing type checking (проверка типов с помощью typing в python)

В Python 3.10+ можно писать короче:

def parse_number(text: str) -> int | float:
    ...

Python type dict (тип dict в python)

Частая ошибка: забыть импортировать Union (в старых версиях) или использовать | в Python 3.9 и ниже, что вызовет синтаксическую ошибку. Также важно помнить, что Union не должен содержать повторяющихся типов (это избыточно).

Как показать, что функция может вернуть None или значение определённого типа?

Специальная конструкция Optional[тип] (эквивалент Union[тип, None]). Рекомендуется использовать тип | None в Python 3.10+.

from typing import Optional

def find_user(user_id: int) -> Optional[dict]:
    if user_id in database:
        return database[user_id]
    return None

Python return type (тип возврата функции в python)

Ошибка: путаница между Optional и Union. Optional[str] означает Union[str, None], а не „опциональная строка“. Если в возврате допустимы несколько типов плюс None, лучше использовать Union[тип1, тип2, None].

Как аннотировать функцию, возвращающую произвольный тип (динамический возврат)?

Используется тип Any из typing. Однако это снижает эффективность статической проверки.

from typing import Any

def get_data(source: str) -> Any:
    if source == 'config':
        return {'key': 'value'}
    elif source == 'list':
        return [1, 2, 3]
    return None

Злоупотребление Any приводит к потере всех преимуществ аннотаций. Лучше использовать Union или обобщённые типы (TypeVar). Если тип действительно не определён, Any допустимо, но стоит задокументировать причину.

Как указать, что функция возвращает другую функцию?

Используется конструкция Callable[[типы_аргументов], возвращаемый_тип] из модуля typing.

from typing import Callable

def multiplier(factor: float) -> Callable[[float], float]:
    def multiply(x: float) -> float:
        return x * factor
    return multiply

Сложность в записи сигнатуры вложенной функции. Если аргументов много или они неизвестны, можно использовать Callable[..., тип]. Однако это уменьшает точность проверки. Также нужно помнить, что Callable не поддерживает перегрузку – для этого применяются протоколы.

Как описать тип возврата генератора?

Генераторы возвращают объекты, реализующие протокол итерации. Чаще всего используют Iterator[тип] или Generator[тип_yield, тип_send, тип_return].

from typing import Iterator

def count_to(n: int) -> Iterator[int]:
    for i in range(1, n + 1):
        yield i

Если генератор использует send и return, нужен полный Generator:

from typing import Generator

def controlled_counter() -> Generator[int, str, bool]:
    total = 0
    cmd = yield total
    if cmd == 'stop':
        return True
    return False

Ошибка: путать Iterable и Iterator. Iterable – любой объект, который можно перебирать (список, строка), а Iterator – ленивый генератор. Для генераторов следует использовать Iterator, чтобы подчеркнуть, что результат можно перебрать только один раз.

Как аннотировать асинхронную функцию?

Асинхронная функция (async def) возвращает корутину. Тип возврата – Coroutine[Any, Any, тип] из модуля typing или его упрощение – просто указать возвращаемый тип без обёртки, но это менее точно.

from typing import Coroutine
import asyncio

async def fetch_data(url: str) -> Coroutine[Any, Any, dict]:
    # имитация запроса
    await asyncio.sleep(1)
    return {'status': 200}

Современные анализаторы (mypy) часто принимают упрощённый вариант:

async def fetch_data(url: str) -> dict:
    ...

Проблема: если не указать тип корутины явно, статическая проверка может быть менее строгой. Для точной проверки рекомендуется использовать Coroutine или вспомогательные типы из collections.abc.

Как сделать функцию, возвращающую тот же тип, что и аргумент (обобщённая функция)?

Используется TypeVar для связывания типов входа и выхода.

from typing import TypeVar

T = TypeVar('T')

def identity(value: T) -> T:
    return value

Можно ограничить TypeVar с помощью bound или указать несколько TypeVar для разных позиций.

Распространённая ошибка: не определять TypeVar в области видимости (должен быть на уровне модуля) и не связывать типы. Если TypeVar не используется нигде, кроме одного места, лучше использовать Any или конкретный тип.

Как аннотировать функцию, возвращающую разные типы в зависимости от значения аргумента?

Применяется декоратор @overload из модуля typing. Он позволяет описать несколько сигнатур для одной реализации.

from typing import overload

@overload
def parse(data: str) -> int: ...

@overload
def parse(data: bytes) -> str: ...

def parse(data: str | bytes) -> int | str:
    if isinstance(data, str):
        return int(data)
    return data.decode()

Проблема: перегрузка – это только подсказка для анализатора; реальная реализация должна обрабатывать все случаи самостоятельно. Если пропустить ветку, mypy не предупредит. Также нужно следить за порядком перегрузок (более конкретные должны идти первыми).

Дополнительные примеры аннотаций возврата

Пример 1. Использование Union и проверка типов в рантайме.

Пример
from typing import Union

def to_number(value: str) -> Union[int, float, None]:
    try:
        if '.' in value:
            return float(value)
        return int(value)
    except ValueError:
        return None

# вызов
result = to_number('42')
print(result)                       # 42
print(type(result))                 # 
42

Пример 2. Фабрика функций с Callable и замыканием.

Пример
from typing import Callable

def create_power_function(exponent: int) -> Callable[[int], int]:
    def power(base: int) -> int:
        return base ** exponent
    return power

square = create_power_function(2)
cube = create_power_function(3)
print(square(5))   # 25
print(cube(5))     # 125
25
125

Пример 3. Генератор с полной аннотацией Generator.

Пример
from typing import Generator

def sum_coroutine() -> Generator[int, int, int]:
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value
    return total

gen = sum_coroutine()
next(gen)                     # 0
gen.send(10)                  # 10
gen.send(20)                  # 30
try:
    gen.send(None)
except StopIteration as e:
    print('Total:', e.value)  # Total: 30
0
10
30
Total: 30

Пример 4. Обобщённая функция с несколькими TypeVar.

Пример
from typing import TypeVar, Sequence

U = TypeVar('U')
V = TypeVar('V')

def zip_lists(a: Sequence[U], b: Sequence[V]) -> list[tuple[U, V]]:
    return list(zip(a, b))

result = zip_lists([1, 2], ['a', 'b'])
print(result)  # [(1, 'a'), (2, 'b')]
[(1, 'a'), (2, 'b')]

Пример 5. Использование Literal для точных возвращаемых значений.

Пример
from typing import Literal

def get_status(code: int) -> Literal['ok', 'error']:
    if code == 200:
        return 'ok'
    return 'error'

print(get_status(200))  # ok
print(get_status(404))  # error
ok
error

Пример 6. Возврат словаря с фиксированной структурой (TypedDict).

Пример
from typing import TypedDict

class Person(TypedDict):
    name: str
    age: int
    city: str

def create_person(name: str, age: int, city: str) -> Person:
    return {'name': name, 'age': age, 'city': city}

p = create_person('Alice', 30, 'Moscow')
print(p['name'])  # Alice
Alice

Пример 7. Комбинация overload с разными возвращаемыми типами.

Пример
from typing import overload, Union

@overload
def process(item: int) -> int: ...

@overload
def process(item: str) -> str: ...

def process(item: Union[int, str]) -> Union[int, str]:
    if isinstance(item, int):
        return item * 2
    return item.upper()

print(process(10))    # 20
print(process('hi'))  # HI
20
HI

Тип возврата функции в Python - comments

En
Python return type (python)