Модуль typing: аннотации типов для начинающих

Раздел: Основы Python -> Работа с модулями

Основной подход: аннотации типов с помощью модуля typing

Модуль typing предоставляет инструменты для явного указания типов переменных, аргументов функций и возвращаемых значений. Это улучшает читаемость кода и позволяет статическим анализаторам, таким как mypy, находить ошибки до выполнения программы.

Пример базовой аннотации функции:

from typing import List, Tuple, Optional

def process_items(items: List[int], threshold: float = 0.5) -> Tuple[float, Optional[str]]:
    total = sum(items)
    avg = total / len(items)
    if avg > threshold:
        return avg, 'above threshold'
    return avg, None

Python module attributes (атрибуты модуля в python)

В этом примере функция принимает список целых чисел и возвращает кортеж из числа и необязательной строки. Аннотации не влияют на выполнение, но mypy проверяет согласованность типов.

Типичные проблемы:

Если забыть импортировать typing, возникнет NameError. Также mypy может не установлен, и аннотации останутся только документацией. Решение: установить mypy (pip install mypy) и настроить его запуск.

Еще одна ошибка: путать List[int] и list[int] (последнее доступно с Python 3.9 без импорта). Для совместимости со старыми версиями Python рекомендуется использовать typing.

Как указать, что переменная может принимать несколько типов?

Используйте Union.

from typing import Union

def process(value: Union[int, str]) -> str:
    if isinstance(value, int):
        return f'Number: {value}'
    return f'String: {value}'

Python module version (версия модуля python)

Избыточное использование Union может усложнить чтение. Если один из типов None, лучше использовать Optional.

Как обработать значение, которое может быть None?

Optional[T] равносилен Union[T, None].

from typing import Optional

def find_user(id: int) -> Optional[str]:
    if id > 0:
        return 'User'
    return None

Python cpp module (взаимодействие python с модулями c++)

Помните, что Optional подразумевает None, а не просто отсутствие значения. Не путать с аргументами по умолчанию None.

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

Используйте TypeVar и Generic.

from typing import TypeVar, List

T = TypeVar('T')
def get_first(items: List[T]) -> T:
    return items[0]

Python module cv2 (модуль cv2 (opencv) в python)

Неправильное указание ограничений: TypeVar можно ограничить (T = TypeVar('T', int, str)). Без ограничений работает с любым типом.

Как передать функцию как аргумент с типом?

Используйте Callable.

from typing import Callable

def apply(func: Callable[[int, int], int], x: int, y: int) -> int:
    return func(x, y)

Python encodings module (модуль encodings в python)

Callable с многоточием (...) для любых аргументов: Callable[..., int]. Не перепутать с кортежем типов.

Как определить структурный тип (утиную типизацию) для проверки методов объекта?

Используйте 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('Circle drawn')

render(Circle())  # OK

Platform module python (модуль platform в python)

Protocol работает только с структурным соответствием, не с наследованием. mypy проверяет наличие методов, но не требует явного наследования.

Как создать перегруженную функцию с разными сигнатурами?

Используйте декоратор overload.

from typing import overload, Union

@overload
def add(a: int, b: int) -> int: ...
@overload
def add(a: str, b: str) -> str: ...
def add(a: Union[int, str], b: Union[int, str]) -> Union[int, str]:
    if isinstance(a, int) and isinstance(b, int):
        return a + b
    if isinstance(a, str) and isinstance(b, str):
        return a + b
    raise TypeError('Types mismatch')

Python string module (модуль string в python)

Тело функции должно быть совместимо со всеми перегрузками. Если не указать все варианты, mypy выдаст ошибку.

Как ограничить переменную конкретными литеральными значениями?

Используйте Literal.

from typing import Literal

def set_status(status: Literal['active', 'inactive']) -> None:
    print(f'Status: {status}')

Module sys python (модуль sys в python)

Literal доступен с Python 3.8. Не злоупотребляйте, так как сужает гибкость.

Как объявить константу, которую нельзя изменить?

Используйте Final.

from typing import Final

MAX_RETRIES: Final = 3
# MAX_RETRIES = 5  # mypy выдаст ошибку

Python tkinter module (модуль tkinter в python)

Final не предотвращает изменение во время выполнения, только подсказывает анализатору. В Python нет настоящих констант.

Как создать новый тип на основе существующего?

Используйте NewType.

from typing import NewType

UserId = NewType('UserId', int)
def get_user_name(user_id: UserId) -> str:
    return f'User {user_id}'

# mypy принимает только UserId, а не int
name = get_user_name(UserId(42))

NewType создает тип времени проверки, не времени выполнения. Обратите внимание, что UserId(42) создает целое число, но mypy отличает их.

- Python module windows (модуль windows для python)
- Python module path (путь к модулю python)
- Python base modules (базовые модули python)

Продвинутые примеры использования модуля typing

Пример 1: Обобщенный класс контейнер

Создадим обобщенный класс Stack, который поддерживает разные типы элементов.

Пример
from typing import TypeVar, Generic, 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()

# Использование
int_stack = Stack[int]()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop())  # 2
str_stack = Stack[str]()
str_stack.push('hello')
print(str_stack.pop())  # hello
2
hello

Пример 2: Использование Protocol для статической утиной типизации

Определим протокол для объектов, которые можно сериализовать в JSON.

Пример
from typing import Protocol, Dict

class Serializable(Protocol):
    def to_dict(self) -> Dict[str, object]: ...

def serialize(obj: Serializable) -> str:
    import json
    return json.dumps(obj.to_dict())

class User:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age
    
    def to_dict(self) -> Dict[str, object]:
        return {'name': self.name, 'age': self.age}

class Product:
    def __init__(self, title: str, price: float) -> None:
        self.title = title
        self.price = price
    
    def to_dict(self) -> Dict[str, object]:
        return {'title': self.title, 'price': self.price}

# Работает для обоих классов
print(serialize(User('Alice', 30)))
print(serialize(Product('Laptop', 1200.0)))
{'name': 'Alice', 'age': 30}
{'title': 'Laptop', 'price': 1200.0}

Пример 3: Перегрузка функций с разными типами возврата

Реализуем функцию, которая принимает число или строку и возвращает обработанное значение с разными типами.

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

@overload
def transform(value: int) -> str: ...
@overload
def transform(value: str) -> int: ...
def transform(value: Union[int, str]) -> Union[str, int]:
    if isinstance(value, int):
        return str(value * 2)
    elif isinstance(value, str):
        return len(value)
    raise TypeError('Unsupported type')

print(transform(5))       # '10'
print(transform('hello')) # 5
10
5

Пример 4: Кастомный TypeVar с ограничениями

Создадим функцию, которая работает только с числами (int и float).

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

Number = TypeVar('Number', int, float)

def square(value: Number) -> Number:
    return value * value

print(square(4))       # 16
print(square(3.5))     # 12.25
# square('a')          # mypy выдаст ошибку
16
12.25

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

Функция принимает только команды 'start' или 'stop'.

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

Command = Literal['start', 'stop']

def execute(cmd: Command) -> None:
    if cmd == 'start':
        print('Starting...')
    elif cmd == 'stop':
        print('Stopping...')

execute('start')  # OK
# execute('pause') # mypy ошибка
Starting...

Пример 6: Комбинация Generic и Callable

Функция принимает список и функцию преобразования, возвращает список преобразованных значений.

Пример
from typing import TypeVar, List, Callable

T = TypeVar('T')
U = TypeVar('U')

def map_list(items: List[T], func: Callable[[T], U]) -> List[U]:
    return [func(item) for item in items]

result = map_list([1, 2, 3], str)
print(result)  # ['1', '2', '3']
['1', '2', '3']

Пример 7: Final и NewType для безопасности

Определим тип UserId и константу максимальной длины.

Пример
from typing import NewType, Final

UserId = NewType('UserId', int)
MAX_USERS: Final = 1000

def register(user_id: UserId) -> bool:
    if user_id > MAX_USERS:
        return False
    return True

print(register(UserId(500)))  # True
# print(register(500))        # mypy ошибка
True

Модуль typing в Python - comments

En
Python typing module (python)