Типизация данных в Python с помощью typing
Основы работы с модулем typing в Python
Модуль typing предоставляет инструменты для описания типов данных в Python. Аннотации не изменяют работу программы, но помогают статическим анализаторам и другим разработчикам понимать ожидаемые значения.
Как аннотировать переменную и функцию в простейшем случае?
Основной подход к типизации в Python заключается в записи аннотаций после двоеточия для параметров и после стрелки для результата.
from typing import List
def average(values: List[float]) -> float:
return sum(values) / len(values)
scores = [90.5, 100.0, 78.0]
print(average(scores))значение bool python (значение bool в python)
89.5
длина переменной python (длина числа и переменной в python)
Параметр values описан как список чисел с плавающей точкой. Функция возвращает число с плавающей точкой. Если передать список строк, статический анализатор укажет на несоответствие.
Вариант 1: Optional для значений с возможным None
Как указать, что параметр может быть пустым?
Тип Optional[int] означает int или None. Такой вариант применяется для аргументов со значением по умолчанию None и для результатов, которые могут отсутствовать.
from typing import Optional
def parse_number(text: Optional[str]) -> Optional[int]:
if text is None:
return None
try:
return int(text)
except ValueError:
return None
print(parse_number('42'))
print(parse_number(None))
print(parse_number('abc'))
Python максимальное целое число (максимальное целое число в python)
42 None None
Python list dict str str (список словарей со строковыми ключами в python)
Ветвление с проверкой None позволяет сузить тип до str внутри функции.
Частая ошибка: попытка выполнить арифметические операции с результатом Optional[int] без проверки None. Анализатор сообщит о возможной ошибке.
Вариант 2: Union для нескольких разных типов
Как передать в функцию значение одного из нескольких типов?
Union[int, str] принимает int или str. В Python 3.10 поддерживается запись int | str.
from typing import Union
def render(value: Union[int, str]) -> str:
if isinstance(value, int):
return 'Число: ' + str(value)
return 'Строка: ' + value
print(render(5))
print(render('пять'))оператор float python (оператор float в python)
Число: 5 Строка: пять
Type 0 python (тип 0 в python)
Проверка isinstance сужает объединенный тип до конкретного варианта внутри каждой ветки.
Если не выполнить проверку типа, код может вызвать метод, доступный только одному из вариантов.
Вариант 3: Аннотации для коллекций
Как описать список, словарь, кортеж или множество с конкретными типами элементов?
Для этой задачи служат List, Dict, Tuple, Set из модуля typing. В Python 3.9 и новее доступны встроенные list[int], dict[str, int], tuple[int, int], set[str].
from typing import List, Dict, Tuple, Set
def build_config() -> Dict[str, List[int]]:
config = {
'ports': [8000, 8001],
'limits': [10, 20, 30],
}
return config
def pair_scores() -> Tuple[str, int]:
return ('Артем', 5)
def unique_codes() -> Set[int]:
return {101, 102, 103}
print(build_config())
print(pair_scores())
print(unique_codes())S type python (тип s в python)
{'ports': [8000, 8001], 'limits': [10, 20, 30]}
('Артем', 5)
{101, 102, 103}тип элемента python (тип элемента в python)
Вложенные конструкции, такие как Dict[str, List[int]], указывают типы для всех уровней.
Пустая коллекция без типа элементов теряет информацию для анализатора. Стоит записывать пустой список в переменную с аннотацией, например data: list[int] = [].
Вариант 4: Callable для передачи функций
Как описать функцию, которую планируется передать в другую функцию?
Callable[[int, int], int] означает вызываемый объект с двумя аргументами int и возвращаемым значением int. Случай с разной сигнатурой записывается как Callable[..., int].
from typing import Callable
def perform_operation(a: int, b: int, operation: Callable[[int, int], int]) -> int:
return operation(a, b)
def add(x: int, y: int) -> int:
return x + y
def multiply(x: int, y: int) -> int:
return x * y
print(perform_operation(6, 7, add))
print(perform_operation(6, 7, multiply))Code typing python (примеры кода с модулем typing в python)
13 42
целое положительное число python (целое положительное число)
Такой подход применяется в функциях сортировки, обработчиках событий и в декораторах.
Ошибка возникает, если передать функцию с неправильным количеством параметров или несовместимыми типами.
Вариант 5: TypeVar для обобщенных функций
Как сохранить связь между типом входного списка и типом возвращаемого элемента?
TypeVar объявляет переменную типа. Функция get_first(items: List[T]) -> T гарантирует, что тип результата совпадает с типом элементов списка.
from typing import List, TypeVar
T = TypeVar('T')
def get_first(items: List[T]) -> T:
return items[0]
print(get_first([1, 2, 3]))
print(get_first(['a', 'b', 'c']))Int python значения (целочисленные значения (int) в python)
1 a
Set str python (множество из строки в python)
В первом вызове T принимает значение int, во втором str.
Вариант 6: Generic классы
Как построить класс, который работает с разными типами, но сохраняет информацию о типе?
Наследование от Generic[T] превращает класс в обобщенный. Атрибуты и методы могут использовать T как обычный тип.
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def take(self) -> T:
return self.value
number_box = Box(100)
text_box = Box('hello')
print(number_box.take() + 10)
print(text_box.take().upper())Python словарь объектов (словарь объектов в python)
110 HELLO
Str string python (функция str и строковый тип в python)
При создании number_box тип T автоматически выводится как int, для text_box как str.
Вариант 7: Protocol для структурной типизации
Как описать необходимые методы и атрибуты без создания общего базового класса?
Protocol определяет набор методов. Любой класс, который содержит эти методы, считается подходящим, даже без наследования от Protocol.
from typing import Protocol
class Shape(Protocol):
def area(self) -> float:
...
class Rectangle:
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
class Circle:
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
return 3.14159 * self.radius ** 2
def print_area(shape: Shape) -> None:
print(shape.area())
print_area(Rectangle(2, 3))
print_area(Circle(1))6.0 3.14159
Protocol удобен при работе с разными библиотеками, когда не хочется заставлять классы наследовать общий интерфейс.
Вариант 8: Literal и Final
Как ограничить допустимые значения константы и запретить ее переназначение?
Literal['auto', 'manual'] допускает только перечисленные строки. Final[int] указывает, что значение не должно меняться после инициализации.
from typing import Final, Literal
TOTAL_STUDENTS: Final[int] = 30
mode: Literal['auto', 'manual'] = 'auto'
def set_mode(new_mode: Literal['auto', 'manual']) -> None:
global mode
mode = new_mode
print(TOTAL_STUDENTS)
print(mode)
set_mode('manual')
print(mode)30 auto manual
Подобные аннотации полезны для конфигураций, где допустимы строго определенные значения.
Изменение TOTAL_STUDENTS не приведет к ошибке во время выполнения, но статический анализатор зафиксирует нарушение.
Вариант 9: TypedDict для структурированных словарей
Как описать словарь с фиксированным набором ключей и типами значений?
TypedDict задает схему словаря. В отличие от Dict[str, int], здесь перечисляются конкретные ключи.
from typing import TypedDict
class UserProfile(TypedDict):
name: str
age: int
email: str
profile: UserProfile = {
'name': 'Мария',
'age': 27,
'email': 'maria@example.com',
}
print(profile['name'])
print(profile['age'])Мария 27
Если добавить ключ, отсутствующий в схеме, статический анализатор сообщит об ошибке.
Вариант 10: NewType для отдельных типов
Как различить числа с разным смыслом, например идентификатор пользователя и количество?
NewType создает производный тип. Функция с параметром UserId не примет обычный int, хотя во время выполнения user_id остается целым числом.
from typing import NewType
UserId = NewType('UserId', int)
def get_user(identifier: UserId) -> str:
return 'user_' + str(identifier)
user_id = UserId(42)
print(get_user(user_id))user_42
NewType применяется, когда нужно различать на уровне типов разные сущности, хранящиеся как int.
Типичные ошибки при использовании typing
Аннотации типов не выполняются в рантайме. Для проверки соответствия используют mypy или другой статический анализатор.
Ошибка 1: пропущен импорт. Например, использование Optional без строки from typing import Optional приводит к исключению NameError.
Ошибка 2: устаревший синтаксис. Для Python 3.8 нужен typing.List, а для Python 3.9 и новее допустимо list[int].
Ошибка 3: слишком частое использование Any. Any отключает проверку типа и скрывает настоящие проблемы.
Ошибка 4: несовпадение структуры в TypedDict. Словарь должен содержать ровно те ключи и с теми типами, что объявлены.
Дополнительные примеры работы с typing
TypeVar с ограничением bound
Bound ограничивает возможные типы подклассами указанного класса.
from typing import TypeVar
class Shape:
def area(self) -> float:
return 0.0
class Square(Shape):
def __init__(self, side: float) -> None:
self.side = side
def area(self) -> float:
return self.side * self.side
S = TypeVar('S', bound=Shape)
def double_area(shape: S) -> float:
return shape.area() * 2
square = Square(4)
print(double_area(square))32.0
Переменная S может быть только Shape или его наследником.
Перегрузка функций с помощью overload
Overload описывает несколько сигнатур для одной функции. Реализация записывается в последнем определении без декоратора.
from typing import overload
@overload
def parse(item: int) -> int: ...
@overload
def parse(item: str) -> str: ...
def parse(item):
if isinstance(item, str):
return item.lower()
return int(item) * 2
print(parse(21))
print(parse('ABC'))42 abc
Такой подход удобен для библиотек, где функция ведет себя по-разному для разных типов аргументов.
Protocol с проверкой на этапе выполнения
Декоратор runtime_checkable включает поддержку isinstance для протоколов.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Greeter(Protocol):
def greet(self) -> str: ...
class Person:
def greet(self) -> str:
return 'Привет'
print(isinstance(Person(), Greeter))True
Обычный Protocol без runtime_checkable не поддерживает isinstance.
ParamSpec для декораторов с сохранением сигнатуры
ParamSpec позволяет описать декоратор, который передает оригинальные параметры без их потери.
from typing import Callable, ParamSpec, TypeVar
P = ParamSpec('P')
R = TypeVar('R')
def add_logging(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print('Calling')
return func(*args, **kwargs)
return wrapper
@add_logging
def add(a: int, b: int) -> int:
return a + b
print(add(2, 3))Calling 5
Без ParamSpec декоратор получил бы тип Callable[..., Any] и потерял связь между аргументами и результатом.
Вложенные TypedDict структуры
TypedDict можно использовать внутри других TypedDict.
from typing import TypedDict
class Address(TypedDict):
city: str
zip_code: str
class PersonData(TypedDict):
name: str
address: Address
person: PersonData = {
'name': 'Иван',
'address': {
'city': 'Москва',
'zip_code': '101000',
},
}
print(person['address']['city'])Москва
Структура вложенных словарей получает полную аннотацию.
Псевдонимы типов
Для сложных аннотаций удобно вводить псевдоним.
from typing import Dict, List, Tuple, TypeAlias
Point: TypeAlias = Tuple[float, float]
Path: TypeAlias = List[Point]
RouteMap: TypeAlias = Dict[str, Path]
route: RouteMap = {
'main': [(0.0, 0.0), (1.5, 2.0)],
}
print(route){'main': [(0.0, 0.0), (1.5, 2.0)]}Псевдоним делает аннотации более читаемыми.
TypeGuard для сужения типа
TypeGuard указывает, что функция является проверкой типа для конкретного значения.
from typing import Any, TypeGuard, List
def is_str_list(value: List[Any]) -> TypeGuard[List[str]]:
return all(isinstance(item, str) for item in value)
data = ['a', 'b', 'c']
if is_str_list(data):
print(' '.join(data).upper())A B C
Внутри ветки if анализатор считает data списком строк.