Модуль typing: аннотации типов для начинающих
Основной подход: аннотации типов с помощью модуля 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, NonePython 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()) # OKPlatform 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 отличает их.
Продвинутые примеры использования модуля 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()) # hello2 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')) # 510 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