UpPetto

This is a real course generated by UpPetto — unedited.

AI-generated. Two models wrote it, a third checked every risky claim against sources, a fourth re-checked.

Create my own course

Type hints и mypy: сетка безопасности без изменения семантики языка

core аннотировать функцию типами и настроить mypy так, чтобы он ловил ошибки типов до запуска

Утиная типизация гибка, но не бесплатна. Если функция ожидает итерируемое со строками, а ей передали [1, 2, 3] — протокол итерации соблюдён, и ошибка вылезет глубоко внутри, на первом .upper(). Здесь вступают аннотации типов и статический анализ.

Аннотации типов (type hints, PEP 484) выглядят похоже на сигнатуры C:

def greeting(name: str) -> str:
    return 'Hello ' + name

Но по семантике это совсем другая вещь: аннотации — это метаданные, которые CPython во время выполнения в общем случае не проверяет и не использует для диспетчеризации. greeting(123) спокойно дойдёт до тела функции и упадёт (или не упадёт) уже по факту исполнения операции.

Отсюда первая экспертная ошибка C-программиста: увидев type hints, он расслабляется, считая, что «теперь Python типизирован, как C». Это неверно ровно так же, как комментарий не гарантирует, что код ему соответствует. Аннотация — декларация намерения для человека и для внешнего инструмента.

Этим инструментом обычно является mypy (аналоги — pyright, pyre). Он читает те же аннотации и статически, до запуска, проверяет согласованность типов по всей кодовой базе — ту работу, которую в C бесплатно делал компилятор, но как отдельный, осознанно запускаемый шаг. Не запускаете mypy в CI или перед коммитом — аннотации остаются документацией, не защищающей ни от чего в runtime.

graph TD
    A["1. Пишем код с аннотациями"] --> B{"2. Запускаем mypy"}
    B -->|"ошибки найдены"| C["3. mypy: Argument 1 has incompatible type int; expected str"]
    C --> D["4. Исправляем код"]
    D --> A
    B -->|"ошибок нет"| E["5. Запускаем python my_script.py"]
цикл разработки с mypy — проверка типов до запуска, как с компилятором

Не теряется ли при этом гибкость протоколов? Нет: модуль typing даёт абстрактные типы, описывающие поведение, а не конкретные классы:

  • Iterable[T] — всё, что можно итерировать, отдавая элементы типа T;
  • Sequence[T] — есть длина и доступ по индексу (__len__, __getitem__);
  • Mapping[K, V] — поведение словаря;
  • Protocol — формальное описание «утиного типа»: любой класс с нужными методами подходит структурно, без наследования.
from typing import Iterable

def process_strings(items: Iterable[str]) -> None:
    for item in items:
        print(item.upper())   # mypy знает, что item — str

process_strings(["a", "b"])   # OK
process_strings(("c", "d"))   # OK
# process_strings([1, 2, 3])
# error: List item 0 has incompatible type "int"; expected "str"

Практическая связка для C-привычки к строгой типизации:

  1. Аннотируйте публичные функции и границы модулей — там контракт ценнее всего.
  2. Запускайте mypy отдельным шагом, аналогично gcc -Wall -Werror: до тестов, в CI, локально перед коммитом.
  3. Не путайте прохождение mypy с отсутствием runtime-ошибок: mypy не выполняет код и не видит динамику (getattr, setattr, eval, монки-патчинг, данные из json.loads без аннотаций).

Разобранный пример

Функция с типовой C-логикой «валидации диапазона»:

def clamp(value: int, low: int, high: int) -> int:
    if value < low:
        return low
    if value > high:
        return high
    return value

Без mypy это ничем не отличается от урока 1: clamp("abc", 0, 10) попытается сравнить строку с числом и упадёт с TypeError — но только в момент вызова.

Если в файле есть вызов

clamp("abc", 0, 10)

то mypy сообщит об ошибке до запуска программы:

error: Argument 1 to "clamp" has incompatible type "str"; expected "int"

Это то поведение, к которому вы привыкли в C: ошибка найдена статическим анализом всей кодовой базы, а не тем, что строка случайно исполнилась в тесте. Разница лишь в том, что шаг отдельный и необязательный: забыли запустить mypy — вернулись к чистому runtime-поведению из урока 1.

Важный нюанс: mypy проверяет согласованность объявленных типов, а не реальное поведение во время исполнения. Если внутри функции происходит setattr(obj, "x", 1) или объект пришёл из внешнего JSON без аннотации, несоответствие может остаться незамеченным — статический анализ ограничен тем, что выражено в типах явно.

Попробуй сейчас

Сохраните clamp выше в файл clamp.py, добавьте туда вызов clamp("x", 1, 2), установите mypy (pip install mypy) и запустите mypy clamp.py. Убедитесь, что ошибка типа найдена до запуска программы.

Затем измените сигнатуру на value: float и проверьте вызов clamp(1, 0, 10) — убедитесь, что mypy его пропускает (по правилам PEP 484 int принимается там, где ожидается float).

Дополнительно: замените value: int на values: Iterable[int] в новой функции и посмотрите, как mypy реагирует на передачу list[str].

Получилось, если…

Вы поняли материал, если можете объяснить коллеге одним предложением: «аннотации типов ничего не проверяют во время выполнения — проверяет их mypy, отдельным шагом, до запуска, как компилятор C делал это для сигнатур функций», и понимаете, когда писать конкретный тип (list[int]), а когда абстрактный протокол (Iterable[int]).

Вывод

Type hints — метаданные, а не runtime-механизм принуждения. Проверку, аналогичную компилятору C, делает mypy как отдельный статический шаг. Абстрактные типы из typing (Iterable, Sequence, Mapping, Protocol) сохраняют гибкость утиной типизации внутри статической проверки. Забыли запустить mypy — вернулись к чистой динамической типизации из первого урока.

flowchart TD
    A[Код с аннотациями типов] --> B{Запущен mypy?}
    B -->|да| C[Статическая проверка согласованности типов до запуска]
    C --> D[Ошибка найдена до исполнения, как в C]
    B -->|нет| E[Аннотации игнорируются интерпретатором]
    E --> F[Проверка типов только в runtime, как в уроке 1]
аннотации типов работают как защита только вместе с явным запуском mypy

AI-generated · source-grounded review

🛡 Fact-checked: 2 risky claims verified · 4 removed · confidence: high · figures: 2
[verified] Аннотации типов введены PEP 484 и игнорируются CPython во время выполнения
База знаний прямо указывает PEP 484 и что аннотации не влияют на выполнение в CPython
[verified] mypy — внешний статический анализатор, находящий ошибки типов до запуска
Подтверждено разделом об экосистеме и типизации
[softened] int принимается там, где ожидается float (numeric tower)
Правило верно по PEP 484, но термин «структурная совместимость» неточен; переформулировано
[removed] mypy не находит ошибок в коде с max(averages, key=averages.get)
Недостоверно: mypy обычно сообщает о несовместимом типе аргумента key; практика заменена
[removed] Практика версии B (аннотирование calculate_stats с max(averages, key=averages.get)) удалена: утверждение «mypy не должен найти ошибок в текущем коде» недостоверно — mypy обычно жалуется на тип аргумента key
[removed] Из mermaid-схемы версии B убран узел «Уверенность в отсутствии ошибок этого класса»: противоречит тезису урока о том, что прохождение mypy не гарантирует отсутствия runtime-ошибок типов
[removed] Формулировка «структурная совместимость numeric tower» смягчена до «по правилам PEP 484 int принимается там, где ожидается float»
[removed] Пример с @runtime_checkable Protocol сокращён до упоминания Protocol: детальные утверждения о сообщениях mypy про несовпадение сигнатур read не подтверждены базой знаний

A second model re-checked this lesson's claims and corroborated 3 of 4 , and could not confirm 1 either way. This is incomplete corroboration, not a disagreement.

Key concepts: type hints mypy static analysis
Tell me more 🔒 Didn't understand — explain simply 🔒 Show examples 🔒 Sources 🔒

On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course