42. Мягкое введение в подсказки типов (необязательно)
На протяжении этой книги вы писали код Python, не указывая, какие типы данных хранят ваши переменные или какие типы принимают и возвращают ваши функции (function). Python отлично работал и так — это динамически типизированный язык, то есть типы определяются во время выполнения, по мере работы программы. Эта гибкость — одна из главных сильных сторон Python, позволяющая писать код быстро и выразительно.
Однако по мере того, как программы становятся больше и сложнее, эта гибкость иногда может усложнять понимание и сопровождение кода. Когда вы видите функцию вроде def process_data(items):, вы можете задуматься: Какие данные содержит items? Список строк? Словарь? Или вообще что-то другое?
Подсказки типов (type hints) (их также называют аннотациями типов (type annotations)) дают способ задокументировать ожидаемые типы в вашем коде. Это необязательные дополнения к Python, которые могут сделать код понятнее, помочь ловить ошибки раньше и включить мощные возможности IDE — и всё это без изменения того, как Python на самом деле выполняет ваш код.
Эта глава мягко знакомит с подсказками типов, показывая, что это такое, зачем они существуют и как эффективно их использовать. Поскольку подсказки типов необязательны и не влияют на то, как Python выполняет ваш код, вся эта глава помечена как необязательная. Вы можете писать отличные программы на Python, никогда не используя подсказки типов. Но понимание их поможет вам читать современный код Python и решать, когда они могут быть полезны в ваших проектах.
42.1) Почему в Python добавили подсказки типов
Python с самого начала проектировался как динамически типизированный язык. Десятилетиями программисты Python писали код без какой-либо информации о типах, и это прекрасно работало для бесчисленного количества проектов. Так почему же в Python добавили подсказки типов в 2015 году (вместе с Python 3.5)?
Проблема больших кодовых баз
По мере того как Python становился популярнее для крупномасштабных приложений, команды столкнулись с трудностями:
# В большой кодовой базе что эта функция ожидает и что возвращает?
def calculate_discount(customer, items, code):
# ... 50 строк кода ...
return resultНе читая всё тело функции или её документацию, вы не можете понять:
customer— это словарь, пользовательский объект или что-то ещё?items— это список, кортеж или множество?- Какой тип у
code— строка, целое число? - Что возвращает функция — число, словарь или, возможно,
None?
В маленьких программах эта неоднозначность управляемая. Вы легко можете посмотреть, как функция используется в других местах. Но в кодовой базе с тысячами функций в десятках файлов это становится сложно.
Решение: необязательные подсказки типов
Создатели Python решили добавить необязательную систему документирования типов. Ключевое слово — «необязательную»: подсказки типов полностью добровольны. Вы можете использовать их, когда они помогают, игнорировать, когда не помогают, и свободно смешивать аннотированный и неаннотированный код.
Вот простой пример, показывающий базовый синтаксис:
# Без подсказок типов
def add(a, b):
return a + b
# С подсказками типов
def add(a: int, b: int) -> int:
return a + bСинтаксис прямолинеен:
- Двоеточие (
:) после параметра показывает, какого типа он должен быть:a: int - Стрелка (
->) перед двоеточием показывает, какого типа значение возвращает функция:-> int
Теперь посмотрим на наш более ранний пример:
def calculate_discount(customer: dict, items: list, code: str) -> float:
# ... 50 строк кода ...
return resultТеперь это сразу понятно: customer — словарь, items — список, code — строка, а функция возвращает число с плавающей точкой (float).
Не переживайте, если этот синтаксис выглядит незнакомым — мы подробно разберём его в разделах 42.3–42.6. Пока просто обратите внимание, как по одному взгляду можно понять, что функция ожидает и что возвращает.
С подсказками типов или без них функция работает ровно так же — Python не проверяет эти типы во время выполнения. (Мы подробно рассмотрим этот важный момент в разделе 42.2)
Постепенный, прагматичный подход
Система подсказок типов в Python была спроектирована так, чтобы быть:
- Необязательной: вам никогда не нужно использовать подсказки типов
- Постепенной: вы можете добавлять подсказки только к части кода, а не ко всему
- Ненавязчивой: подсказки не меняют то, как Python выполняет ваш код
- Дружественной к инструментам: внешние инструменты могут проверять подсказки, но сам Python игнорирует их во время выполнения
Этот прагматичный подход позволяет Python оставаться гибким, одновременно давая преимущества тем, кому они нужны.
42.2) Золотое правило: нет проверки типов во время выполнения
Самое важное, что нужно понять о подсказках типов, — вот что: Python не принуждает соблюдение подсказок типов во время выполнения. Они носят исключительно информационный характер. Посмотрим, что означает эта шокирующая реальность на практике.
Подсказки типов не предотвращают неверные типы
Рассмотрим эту функцию с подсказками типов:
def greet(name: str) -> str:
return f"Hello, {name}!"
# Это отлично работает, хотя 42 — не строка
result = greet(42)
print(result) # Output: Hello, 42!Подсказка типа явно говорит, что name должен быть строкой, но Python спокойно принимает целое число 42 и выполняет функцию. Python не проверяет подсказку типа — он просто использует переданное значение.
Это принципиально отличается от языков вроде Java или C++, где компилятор проверяет типы перед запуском кода и отказывается запускать программу при несоответствии типов. Подход Python более либеральный: он доверяет вам, что вы передадите правильные типы, но не заставляет.
Проблема: риски динамической типизации остаются
Вот настоящий вызов: даже с подсказками типов динамическая типизация Python означает, что вы всё ещё можете совершать ошибки типов, которые проявляются только во время выполнения:
def calculate_total(prices: list) -> float:
"""Вычислить сумму цен."""
return sum(prices)
# Это работает нормально
print(calculate_total([10.99, 5.50, 3.25])) # Output: 19.74
# Но это падает во время выполнения!
print(calculate_total("not a list")) # TypeError: 'str' object is not iterableПодсказка типа явно говорит, что prices должен быть списком, но Python не мешает вам передать строку. Ошибка появляется только когда код реально выполняется и пытается использовать sum() на строке.
Это может быть действительно неприятно! Мы добавили подсказки типов, чтобы ловить такие проблемы, но риски динамической типизации всё ещё здесь. Ошибки типов могут скрываться в коде до запуска, потенциально проявляясь в продакшене, когда пользователь делает что-то неожиданное.
Так если подсказки типов не предотвращают ошибки во время выполнения, зачем вообще их использовать?
Так для чего нужны подсказки типов?
Подсказки типов могут не менять поведение Python во время выполнения, но они служат важной цели — они дают информацию людям и инструментам, а не самому Python:
- Документация: они говорят, какие типы функция ожидает и возвращает
- Поддержка IDE: ваш редактор может использовать подсказки для автодополнения и показа предупреждений
- Статический анализ (static analysis): внешние инструменты (например, mypy) могут проверять код на ошибки типов до запуска
- Понимание кода: они делают большие кодовые базы проще для чтения и сопровождения
Думайте о подсказках типов как о комментариях, которые инструменты умеют понимать. Они не меняют то, как Python выполняется, но помогают писать более качественный код.
Но как это на практике помогает нам ловить те ошибки времени выполнения, которые мы только что увидели?
Решение: подсказки типов + поддержка IDE
Вот где подсказки типов действительно раскрываются. Хотя Python не принуждает их во время выполнения, ваша IDE может поймать ошибки ещё до того, как вы запустите код:
def add_numbers(a: int, b: int) -> int:
"""Сложить два числа."""
return a + b
# Здесь ваша IDE покажет предупреждение (до запуска кода)
result = add_numbers("Hello", "World") # IDE: Warning - expected int, got strРедактор кода видит подсказки типов и может предупреждать о несоответствиях типов по мере набора, задолго до запуска кода. Это ловит многие баги на этапе разработки, а не в продакшене.
Современная разработка на Python обычно работает так:
- Вы пишете код с подсказками типов
- IDE показывает предупреждения, когда типы не совпадают
- Вы исправляете проблемы до запуска кода
- Ошибки времени выполнения из-за несоответствий типов становятся гораздо реже
Подсказка типа не предотвращает ошибку во время выполнения, но ваша IDE использует её, чтобы не дать вам изначально написать багованный код!
Лучшее из двух миров
Подсказки типов дают Python лучшее из двух миров — раннее обнаружение большинства ошибок при сохранении гибкости:
Безопасность разработки: ваша IDE и проверяющие типы (type checkers) ловят большинство ошибок типов на этапе разработки, так что вы находите баги рано.
def process(data: list) -> list:
return [x * 2 for x in data]
# Если вы случайно передадите строку:
process("hello") # IDE warns: expected list, got str
# Вы исправите это до запуска кода!Гибкость во время выполнения: Python по-прежнему выполняет код с несоответствием типов, что может быть полезно для быстрого прототипирования или когда вы намеренно хотите принимать несколько типов.
def add_numbers(a: int, b: int) -> int:
return a + b
# Python выполнит это, хотя типы не совпадают
print(add_numbers(5.5, 3.2)) # Output: 8.7 (works!)
print(add_numbers("Hi", " there")) # Output: Hi there (also works!)Эта гибкость означает, что вы не заперты в жёсткой системе типов. Когда нужно нарушить правила (для тестирования, прототипирования или легитимных сценариев), Python позволяет это. Но когда вы пишете продакшн-код, IDE помогает оставаться в безопасности.
Помните золотое правило: подсказки типов не меняют поведение Python во время выполнения — они просто дают вам и вашим инструментам информацию, нужную, чтобы ловить проблемы раньше. Вам всё равно нужно быть осторожным, но теперь у вас есть мощные союзники, которые прикрывают спину.
42.3) Аннотирование функций: параметры и возвращаемые значения
Самое частое применение подсказок типов — аннотирование параметров функции и возвращаемых значений. Это говорит читателям (и инструментам), какие типы функция ожидает и производит. Начнём с простейшего случая и будем постепенно усложнять.
Основы: аннотации параметров
Чтобы добавить подсказку типа к параметру, поставьте двоеточие после имени параметра и затем укажите тип:
def greet(name: str):
"""Поприветствовать человека по имени."""
return f"Hello, {name}!"
# Использование
message = greet("Alice")
print(message) # Output: Hello, Alice!Синтаксис name: str означает «параметр name должен быть строкой». Вы можете добавлять подсказки типов к нескольким параметрам:
def calculate_area(width: float, height: float):
"""Вычислить площадь прямоугольника."""
return width * height
# Использование
area = calculate_area(5.0, 3.0)
print(area) # Output: 15.0Здесь и width, и height аннотированы как float. Функция работает так же, как и раньше — подсказки типов не меняют поведение — но теперь ваша IDE знает, какие типы ожидать.
Добавление аннотаций возвращаемого типа
Чтобы указать, какой тип возвращает функция, добавьте -> type после списка параметров и перед двоеточием:
def get_full_name(first: str, last: str) -> str:
"""Объединить имя и фамилию."""
return f"{first} {last}"
# Использование
name = get_full_name("John", "Doe")
print(name) # Output: John Doe-> str означает «эта функция возвращает строку». Аннотации возвращаемого типа особенно полезны, когда тип возвращаемого значения неочевиден из имени функции:
def is_adult(age: int) -> bool:
"""Проверить, является ли кто-то взрослым (18 лет или старше)."""
return age >= 18
# Использование
adult = is_adult(25)
print(adult) # Output: TrueДаже не глядя на реализацию, вы сразу знаете, что эта функция возвращает булево значение.
Собираем вместе: полноценная функция
У большинства функций будут и аннотации параметров, и аннотация возвращаемого типа. Вот как выглядит полностью аннотированная функция:
def calculate_discount(price: float, discount_percent: float) -> float:
"""Вычислить цену со скидкой."""
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Использование
original_price = 100.0
discount = 20.0
final_price = calculate_discount(original_price, discount)
print(f"Final price: ${final_price:.2f}") # Output: Final price: $80.00Эта сигнатура функции говорит всё, что вам нужно знать:
- Она принимает два параметра
float:priceиdiscount_percent - Она возвращает значение
float - Вам не нужно читать реализацию, чтобы понять, как использовать эту функцию
Посмотрим ещё один пример с другими типами:
def repeat_message(message: str, times: int) -> str:
"""Повторить сообщение заданное количество раз."""
return message * times
# Использование
repeated = repeat_message("Hello! ", 3)
print(repeated) # Output: Hello! Hello! Hello! Подсказки типов ясно показывают, что вы передаёте строку и целое число, а получаете обратно строку.
Работа со значениями по умолчанию
Когда у параметра есть значение по умолчанию, разместите подсказку типа между именем параметра и значением по умолчанию:
def create_greeting(name: str, formal: bool = False) -> str:
"""Создать приветственное сообщение."""
if formal:
return f"Good day, {name}."
return f"Hi, {name}!"
# Использование
print(create_greeting("Alice")) # Output: Hi, Alice!
print(create_greeting("Bob", formal=True)) # Output: Good day, Bob.Синтаксис formal: bool = False означает «formal — булево значение со значением по умолчанию False».
Можно иметь несколько параметров со значениями по умолчанию — все аннотированные:
def format_price(amount: float, currency: str = "USD", decimals: int = 2) -> str:
"""Отформатировать цену с символом валюты."""
if currency == "USD":
symbol = "$"
elif currency == "EUR":
symbol = "€"
else:
symbol = currency
return f"{symbol}{amount:.{decimals}f}"
# Использование
print(format_price(99.99)) # Output: $99.99
print(format_price(99.99, "EUR")) # Output: €99.99
print(format_price(99.995, "USD", 3)) # Output: $99.995Каждый параметр явно показывает свой тип и значение по умолчанию, делая функцию простой для понимания и использования.
Особый случай: функции, которые не возвращают значения
Некоторые функции только выполняют действия (например, печатают или записывают в файл) и не возвращают значение. Чтобы явно показать, что такие функции ничего не возвращают, используйте -> None:
def print_report(title: str, data: list) -> None:
"""Напечатать форматированный отчёт."""
print(f"=== {title} ===")
for item in data:
print(f" - {item}")
# Нет оператора return, поэтому неявно возвращается None
# Использование
print_report("Sales Data", [100, 150, 200])Output:
=== Sales Data ===
- 100
- 150
- 200Аннотация -> None явно указывает, что эта функция не возвращает осмысленного значения.
Зачем использовать -> None?
- Ясность: это явно выражает ваше намерение — эта функция для действий, а не для результата
- Поддержка IDE: IDE может предупредить, если вы случайно попытаетесь использовать возвращаемое значение
42.4) Простые аннотации переменных
Хотя подсказки типов чаще всего используют с функциями, вы также можете аннотировать переменные. Посмотрим, как это работает и когда это действительно полезно.
Базовый синтаксис аннотаций переменных
Чтобы аннотировать переменную, используйте тот же синтаксис с двоеточием, что и для параметров функции:
# Аннотирование переменных
name: str = "Alice"
age: int = 30
height: float = 5.7
is_student: bool = True
print(f"{name} is {age} years old") # Output: Alice is 30 years oldСинтаксис name: str = "Alice" означает «переменная name — строка и имеет значение 'Alice'». Аннотация не меняет то, как работает переменная — она исключительно информационная.
Аннотации переменных часто пропускают
На практике аннотации переменных используются редко. Причина проста: Python может вывести тип из значения, поэтому аннотации обычно избыточны:
# Эти аннотации не нужны
name: str = "Alice" # Очевидно, строка
count: int = 0 # Очевидно, int
prices: list = [10.99, 5.50] # Очевидно, a list
settings: dict = {} # Очевидно, a dict
# Просто пишите так
name = "Alice"
count = 0
prices = [10.99, 5.50]
settings = {}Когда вы пишете name = "Alice", и вы, и ваша IDE сразу понимаете, что это строка. Аннотация не добавляет полезной информации.
В реальном коде Python вы редко увидите аннотации переменных. Это нормально и ожидаемо. Аннотации функций куда важнее и встречаются гораздо чаще.
Единственный полезный случай: объявление переменных до присваивания
Есть одна ситуация, где аннотации переменных действительно полезны: когда вам нужно объявить переменную до того, как присвоить ей значение.
def calculate_statistics(numbers: list) -> dict:
"""Вычислить базовую статистику по списку чисел."""
# Объявить переменные до их использования
total: float
count: int
average: float
# Теперь присвоить значения
total = sum(numbers)
count = len(numbers)
average = total / count if count > 0 else 0.0
return {
"total": total,
"count": count,
"average": average
}
# Использование
result = calculate_statistics([10, 20, 30, 40])
print(f"Average: {result['average']}") # Output: Average: 25.0Без аннотаций вы не можете объявить переменную, не присвоив ей одновременно значение. Аннотации позволяют заранее указать типы, что может сделать структуру кода понятнее.
Это основной практический сценарий использования аннотаций переменных.
Помните: переменные можно переназначать на разные типы
Даже с аннотацией типа вы можете переназначить переменную на другой тип:
# Начинаем со строки
value: str = "hello"
print(value) # Output: hello
# Переназначаем на другой тип — Python это позволяет
value = 42
print(value) # Output: 42
# Ещё одно изменение типа — всё ещё разрешено
value = [1, 2, 3]
print(value) # Output: [1, 2, 3]Ваша IDE или статический проверяющий типов (type checker) предупредит вас о таких изменениях типов, но сам Python не запрещает их. Подсказки типов направляют вас к согласованности, но не принуждают во время выполнения.
42.5) Работа с None: Optional-типы и оператор |
Один из самых частых паттернов в Python — функция, которая может вернуть значение или может вернуть None. Например, поиск элемента может завершиться успехом (вернув элемент) или неудачей (вернув None). Подсказки типов дают понятные способы выразить этот паттерн.
Проблема: функции, которые могут возвращать None
Рассмотрим эту функцию, которая ищет пользователя:
def find_user_by_email(email: str) -> dict:
"""Найти пользователя по адресу электронной почты."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None # Несоответствие типов! Это противоречит подсказке -> dict
# Использование
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
else:
print("User not found")Подсказка типа -> dict вводит в заблуждение, потому что функция может вернуть None. Статический проверяющий типов предупредит, что возврат None не соответствует объявленному возвращаемому типу dict.
Решение: использование оператора | для optional-типов
В Python 3.10 появился оператор | для подсказок типов, который означает «или». Вы можете использовать его, чтобы указать, что функция может вернуть один тип или другой:
def find_user_by_email(email: str) -> dict | None:
"""Найти пользователя по адресу электронной почты. Возвращает None, если не найден."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None
# Использование
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
missing = find_user_by_email("charlie@example.com")
if missing is None:
print("User not found") # Output: User not foundПодсказка типа -> dict | None означает «эта функция возвращает либо словарь, либо None». Это точно описывает поведение функции.
Примечание: В более старом коде Python (до 3.10) вместо dict | None вы можете встретить Optional[dict] из модуля typing. Это означает то же самое, но | — современный и предпочтительный синтаксис.
Использование | с несколькими типами
Можно использовать |, чтобы указать более двух возможных типов:
def parse_value(text: str) -> int | float | None:
"""Преобразовать строку в число. Возвращает None, если преобразование не удалось."""
try:
# Сначала пробуем распарсить как целое число
if '.' not in text:
return int(text)
# Иначе парсим как float
return float(text)
except ValueError:
return None
# Использование
print(parse_value("42")) # Output: 42 (int)
print(parse_value("3.14")) # Output: 3.14 (float)
print(parse_value("invalid")) # Output: NoneПодсказка типа -> int | float | None означает, что функция может вернуть целое число, число с плавающей точкой или None.
Проверка на None: лучшие практики
Когда функция может вернуть None, всегда проверяйте на None перед использованием результата. Иначе вы рискуете ошибками, пытаясь использовать None так, как будто это ожидаемый тип:
def get_user_age(user_id: int) -> int | None:
"""Получить возраст пользователя. Возвращает None, если пользователь не найден."""
users = {1: 25, 2: 30, 3: 35}
return users.get(user_id)
# Всегда проверяйте на None перед использованием значения
age = get_user_age(1)
if age is not None:
print(f"User is {age} years old") # Output: User is 25 years old
if age >= 18:
print("User is an adult") # Output: User is an adult
else:
print("User not found")
# Для несуществующих пользователей
age = get_user_age(999)
if age is None:
print("User not found") # Output: User not foundКлючевой момент — использовать if age is not None: или if age is None:, чтобы явно проверять перед использованием значения.
Необязательные параметры с | None
Можно использовать | и с параметрами, часто вместе со значениями по умолчанию:
def format_name(first: str, middle: str | None = None, last: str = "") -> str:
"""Отформатировать полное имя. Middle name необязательное."""
if middle and last:
return f"{first} {middle} {last}"
elif last:
return f"{first} {last}"
return first
# Использование
print(format_name("John", "Q", "Doe")) # Output: John Q Doe
print(format_name("Jane", None, "Smith")) # Output: Jane Smith
print(format_name("Prince")) # Output: PrinceПодсказка типа middle: str | None = None показывает, что middle может быть строкой или None, при этом None — значение по умолчанию. Это распространённый паттерн для необязательных параметров.
42.6) Чтение распространённых подсказок типов: list, dict, tuple
Читая код Python, написанный другими, вы будете встречать подсказки типов для коллекций (collections) — таких как списки, словари и кортежи. Современный Python даёт понятные способы указывать не только то, что это список, но и какой тип элементов содержит список.
Примечание: Показанный здесь синтаксис (list[int], dict[str, int] и т. п.) работает в Python 3.9+. В более старом коде вы можете увидеть List[int] и Dict[str, int] (с заглавной буквы) из модуля typing — они работают так же.
Базовые подсказки типов для коллекций
Самые простые подсказки типов для коллекций просто указывают тип коллекции:
def print_items(items: list) -> None:
"""Вывести все элементы списка."""
for item in items:
print(item)
def get_user_settings() -> dict:
"""Получить настройки пользователя в виде словаря."""
return {"theme": "dark", "notifications": True}
def get_position() -> tuple:
"""Получить позицию x, y."""
return (10, 20)Эти подсказки говорят тип коллекции, но не говорят, что находится внутри.
Списки: указание типов элементов
Чтобы указать, какого типа элементы содержит список (list), используйте квадратные скобки:
def calculate_total(prices: list[float]) -> float:
"""Вычислить общую сумму всех цен."""
return sum(prices)
# Использование
total = calculate_total([10.99, 5.50, 3.25])
print(f"Total: ${total:.2f}") # Output: Total: $19.74Подсказка типа list[float] означает «список, содержащий числа с плавающей точкой». Это информативнее, чем просто list.
Вот ещё пример со строками:
def format_names(names: list[str]) -> str:
"""Отформатировать список имён как строку, разделённую запятыми."""
return ", ".join(names)
# Использование
students = ["Alice", "Bob", "Charlie"]
print(format_names(students)) # Output: Alice, Bob, CharlieПодсказка типа list[str] означает «список, содержащий строки».
Словари: указание типов ключей и значений
Для словарей указывайте и тип ключей, и тип значений:
def get_student_grades() -> dict[str, int]:
"""Получить соответствие имён студентов их оценкам."""
return {
"Alice": 95,
"Bob": 87,
"Charlie": 92
}
# Использование
grades = get_student_grades()
for name, grade in grades.items():
print(f"{name}: {grade}")Output:
Alice: 95
Bob: 87
Charlie: 92Подсказка типа dict[str, int] означает «словарь со строковыми ключами и целочисленными значениями».
Вот пример, где значения могут быть нескольких типов:
def get_user_data(user_id: int) -> dict[str, str | int]:
"""Получить данные пользователя. Значения могут быть строками или целыми числами."""
return {
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"id": 12345
}
# Использование
user = get_user_data(1)
print(f"{user['name']} is {user['age']} years old") # Output: Alice is 30 years oldПодсказка типа dict[str, str | int] означает «словарь со строковыми ключами и значениями, которые являются либо строками, либо целыми числами».
Кортежи: фиксированная и переменная длина
Кортежи отличаются от списков тем, что часто имеют фиксированную структуру. Вы можете указать тип для каждой позиции:
def get_user_info(user_id: int) -> tuple[str, int, bool]:
"""
Получить информацию о пользователе в виде кортежа.
Возвращает: (name, age, is_active)
"""
return ("Alice", 30, True)
# Использование
name, age, active = get_user_info(1)
print(f"{name}, {age}, active: {active}") # Output: Alice, 30, active: TrueПодсказка типа tuple[str, int, bool] означает «кортеж ровно из трёх элементов: строка, целое число и булево значение в таком порядке».
Для кортежей переменной длины с элементами одного и того же типа используйте многоточие (...):
# Кортеж фиксированной длины: ровно 2 floats
def get_2d_point() -> tuple[float, float]:
"""Получить 2D-координаты (x, y)."""
return (10.5, 20.3)
# Кортеж переменной длины: любое количество floats
def get_coordinates() -> tuple[float, ...]:
"""Получить координаты. Может быть 2D, 3D или любой размерности."""
return (10.5, 20.3, 15.7) # В данном случае 3D
# Использование
point = get_2d_point()
coords = get_coordinates()
print(f"2D point: {point}") # Output: 2D point: (10.5, 20.3)
print(f"Coordinates: {coords}") # Output: Coordinates: (10.5, 20.3, 15.7)Подсказка типа tuple[float, ...] означает «кортеж, содержащий любое количество элементов типа float». ... означает «любое количество элементов этого типа».
Вложенные коллекции
Вы можете вкладывать подсказки типов для сложных структур данных. Начнём с простого примера:
def get_scores_by_student() -> dict[str, list[int]]:
"""Получить результаты тестов для каждого студента."""
return {
"Alice": [95, 87, 92],
"Bob": [88, 91, 85],
"Charlie": [90, 88, 94]
}
# Использование
scores = get_scores_by_student()
for name, tests in scores.items():
average = sum(tests) / len(tests)
print(f"{name}: {average:.1f}")Output:
Alice: 91.3
Bob: 88.0
Charlie: 90.7Подсказка типа dict[str, list[int]] означает «словарь со строковыми ключами и значениями в виде списка целых чисел».
Вот более сложный пример:
def get_student_records() -> list[dict[str, str | int]]:
"""Получить список записей студентов."""
return [
{"name": "Alice", "age": 20, "major": "CS"},
{"name": "Bob", "age": 21, "major": "Math"},
{"name": "Charlie", "age": 19, "major": "Physics"}
]
# Использование
students = get_student_records()
for student in students:
print(f"{student['name']}, {student['age']}, {student['major']}")Output:
Alice, 20, CS
Bob, 21, Math
Charlie, 19, PhysicsПодсказка типа list[dict[str, str | int]] означает «список словарей, где каждый словарь имеет строковые ключи и значения, которые являются либо строками, либо целыми числами».
Как читать подсказки типов: краткая памятка
Когда вы встречаете подсказки типов в коде, вот как их читать:
Коллекции:
list[int]— «список целых чисел»dict[str, float]— «словарь со строковыми ключами и значениями float»tuple[str, int]— «кортеж ровно из двух элементов: строка, затем целое число»tuple[float, ...]— «кортеж, содержащий любое количество float»
Optional и несколько типов:
int | None— «целое число или None»str | int | float— «строка, целое число или float»
Вложенные:
list[dict[str, int]]— «список словарей (каждый dict со строковыми ключами и целочисленными значениями)»dict[str, list[float]]— «словарь со строковыми ключами и значениями в виде списка float»
Примечание: В более старом коде (Python < 3.10) вы можете встретить Union[int, str] вместо int | str или Optional[int] вместо int | None. Они означают то же самое.