Python: от данных до своей сводки Урок 54 из 56
Аннотации типов и mypy: ошибка до запуска
Пятьдесят третий урок курса по Python. Описываем форму данных в сигнатурах, сужаем `None` проверкой и запускаем `mypy`, чтобы несовместимые значения находились до выполнения программы.
Зачем это нужно
Python остаётся динамическим: аннотация не запрещает передать строку вместо числа во время выполнения. Но редактор и mypy могут проследить путь значения заранее. Особенно полезны типы на границах модулей: в аргументах, результате функции и структуре записи.
Сразу целиком
from dataclasses import dataclass
@dataclass(frozen=True)
class Observation:
source_id: str
year: int
value: float
def latest(rows: list[Observation]) -> Observation | None:
if not rows:
return None
return max(rows, key=lambda row: row.year)
def label(row: Observation | None) -> str:
if row is None:
return "нет данных"
return f"{row.year}: {row.value:.1f} %"
print(label(latest([Observation("cpi-kz", 2025, 11.4)])))
Вывод:
2025: 11.4 %
Сигнатура -> Observation | None честно сообщает о пустом списке. После if row is None анализатор сужает тип: ниже row уже точно Observation.
Проверка mypy
Установите зафиксированную для курса версию и проверьте файл:
python -m pip install mypy==2.3.1
python -m mypy --strict digest.py
Если написать Observation("cpi-kz", "2025", 11.4), проверка укажет, что второй аргумент должен быть int. Код при этом Python запустить может — это и есть различие между временем анализа и временем выполнения.
Начните со своих модулей, а не со всех библиотек сразу. Полезная минимальная настройка pyproject.toml:
[tool.mypy]
python_version = "3.12"
strict = true
files = ["sholu", "main.py"]
Версия здесь — минимальная версия Python, которую обещает проект, а не случайная версия на компьютере автора.
Коллекции и понятные псевдонимы
SourceID = str
Evidence = dict[SourceID, float]
def missing(required: set[SourceID], known: Evidence) -> set[SourceID]:
return required - known.keys()
Псевдоним делает намерение видимым, но SourceID = str не создаёт новый тип: обычная строка всё ещё подходит. Когда перепутать две строки опасно, позже можно применить NewType.
Не прячьте неизвестное в Any
Any выключает проверку для значения и распространяется дальше. Ответ JSON действительно неизвестен на границе, поэтому сначала держите его как object, проверяйте структуру, а затем создавайте типизированную запись. Не объявляйте весь словарь dict[str, Any] только ради тишины.
Аннотация также не проверяет диапазон, единицу измерения или существование source_id. Это по-прежнему работа валидации и тестов. Типы дополняют урок 52, а не заменяют его.
Карта урока
Опорный сигнал: граница → точный тип → сужение → mypy → тест выполнения.
Скажите своими словами
- Почему аннотация не является проверкой во время выполнения?
- Что означает
Observation | None? - Зачем проверять
Noneдо обращения к полям? - Почему
Anyследует ограничивать границей системы?
Разминка
1. Предскажите вывод.
def size(values: list[int]) -> int:
return len(values)
print(size([3, 5]))
2. Заполните результат функции.
def title(year: int) ... str:
return f"Отчёт {year}"
3. Почините. row может быть None:
def show(row: Observation | None) -> str:
return str(row.year)
Задание
Обязательное. Типизируйте latest и label, затем проверьте обычный и пустой список. Запустите python -m mypy --strict solution.py и сам файл.
2025: 11.4 %
нет данных
На своих данных. Добавьте типы трём публичным функциям сводки и устраните ошибки без # type: ignore.
По желанию. Создайте NewType("SourceID", str) и посмотрите, где анализатор найдёт перепутанный идентификатор.
Куда это встанет в проекте
Шаг 28 получает типы на границе модели и строгую настройку mypy. Следующий урок фиксирует Python и зависимости, чтобы тот же анализ и тесты повторились на другом компьютере.
Ответы
Показать ответы
- Python обычно не исполняет аннотации как ограничения; их читает анализатор.
- Функция возвращает запись или отсутствие результата.
- Проверка сужает тип и предотвращает обращение к полю отсутствующего объекта.
Anyотключает полезные проверки и распространяет неизвестность.
2
- Нужны
->.
def title(year: int) -> str:
return f"Отчёт {year}"
print(title(2025))
Отчёт 2025
- Сначала обработайте
None.
def show(row: Observation | None) -> str:
if row is None:
return "нет данных"
return str(row.year)
Источники
- Python: typing — стандартные аннотации.
- mypy: Getting started — статическая проверка.
- mypy: The
Anytype — границы динамической типизации.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.