Shanraq.org Shanraq.org
Аннотации типов и mypy: ошибка до запуска
IT

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 → тест выполнения.

Скажите своими словами

  1. Почему аннотация не является проверкой во время выполнения?
  2. Что означает Observation | None?
  3. Зачем проверять None до обращения к полям?
  4. Почему 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 и зависимости, чтобы тот же анализ и тесты повторились на другом компьютере.

Ответы

Показать ответы
  1. Python обычно не исполняет аннотации как ограничения; их читает анализатор.
  2. Функция возвращает запись или отсутствие результата.
  3. Проверка сужает тип и предотвращает обращение к полю отсутствующего объекта.
  4. Any отключает полезные проверки и распространяет неизвестность.
2
  1. Нужны ->.
def title(year: int) -> str:
    return f"Отчёт {year}"

print(title(2025))
Отчёт 2025
  1. Сначала обработайте None.
def show(row: Observation | None) -> str:
    if row is None:
        return "нет данных"
    return str(row.year)

Источники

Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом

Проверить задание

Сначала решите и запустите в VS Code — редактор покажет ошибку на месте. Готовое решение вставьте сюда. Проверяет модель: она укажет на ошибку, но не даст готовый ответ.

Чтобы проверить, нужно войти. Войти

Комментарии (0)

Пока нет комментариев. Будьте первым.