Mypy в проекте без типов: с чего начать, чтобы не утонуть
Как подключить mypy к кодовой базе, где аннотаций нет вообще, и не получить пять тысяч ошибок за один прогон. Настройка, стратегия по модулям и первые проверки.

Вы запускаете mypy . на проекте, которому пять лет, и получаете 4200 ошибок. Закрыть их разом невозможно, игнорировать — бессмысленно. Ниже — как подключить mypy к нетипизированной базе так, чтобы он начал приносить пользу с первого дня, а не висел красной стеной в CI. На настройку уйдёт около часа, дальше типы добавляются постепенно, модуль за модулем.
Всё ниже проверено на mypy 1.13 и Python 3.11+. Синтаксис конфигурации и имена флагов между минорными версиями mypy иногда меняются, поэтому названия опций сверяйте с документацией вашей версии.
Что нужно до начала
- Python 3.9 или новее — на более старых версиях часть синтаксиса аннотаций требует
from __future__ import annotations. - Виртуальное окружение проекта, в котором стоят все зависимости. mypy проверяет импорты, и без установленных пакетов он завалит вас ошибками про отсутствующие модули.
- Установленный mypy:
pip install mypy. Ставьте его в то же окружение, что и проект, иначе он не увидит зависимости. - Файл конфигурации проекта: подойдёт
pyproject.tomlили отдельныйmypy.ini. Ниже примеры дляpyproject.toml.
Главная ошибка новичка — строгий режим сразу
Соблазн включить strict = true и разобрать всё за выходные заканчивается тем, что проверку отключают через неделю. Стратегия обратная: включить mypy в мягком режиме, чтобы он ловил реальные баги, а не отсутствие аннотаций, и постепенно ужесточать настройки по мере покрытия кода типами.
mypy на нетипизированном коде без настройки — это генератор шума. Ценность появляется, только когда вы отделяете «здесь настоящая ошибка» от «здесь просто нет аннотации».
Шаг 1. Минимальный конфиг
Добавьте в pyproject.toml базовую секцию. Она запускает mypy в режиме, где он не требует аннотаций, но проверяет то, что уже видит.
[tool.mypy]
python_version = "3.11"
# не заваливаемся на импортах пакетов без заглушек типов
ignore_missing_imports = true
# показывать код каждой ошибки, чтобы точечно глушить
show_error_codes = true
# не требовать аннотаций у всех функций подряд
disallow_untyped_defs = false
# но проверять тело функций, у которых аннотации уже есть
check_untyped_defs = true
Проверка: запустите mypy . из корня проекта. Ошибок должно стать в разы меньше, чем в «голом» прогоне. Если их всё ещё тысячи — скорее всего, не установлены зависимости в текущем окружении.
Шаг 2. Прогнать и прочитать ошибки
Оставшиеся ошибки в мягком режиме — это в основном настоящие проблемы: обращение к атрибуту, которого нет, вызов None, несовместимые типы там, где аннотации уже проставлены. Каждая строка выглядит так:
app/orders.py:42: error: Item "None" of "Optional[User]" has no attribute "email" [union-attr]
Код в квадратных скобках (union-attr) — это то, чем вы будете управлять дальше: точечно глушить или чинить.
Шаг 3. Заморозить текущее состояние
Чтобы CI не падал на старых ошибках, но ловил новые, сгенерируйте базовый список того, что вы пока не трогаете. mypy умеет писать отчёты, но самый простой способ на старте — глушить целые проблемные модули в конфиге, а не отдельные строки:
[[tool.mypy.overrides]]
# модули, которые пока не типизируем
module = [
"app.legacy.*",
"app.reports.export",
]
ignore_errors = true
Проверка: после этого mypy . должен завершаться с нулём ошибок. Теперь mypy можно ставить в CI как блокирующую проверку — он будет падать только на новом коде и на модулях, которые вы сознательно вывели из-под ignore_errors.
Как двигаться дальше: модуль за модулем
Стратегия «сверху вниз» по зависимостям работает лучше всего. Начинайте с модулей, от которых зависят другие: типы в них подскажут mypy контекст для остального кода.
- Уберите модуль из блока
ignore_errors. - Прогоните
mypy путь/к/модулю.py— увидите его ошибки изолированно. - Проставьте аннотации сигнатур: аргументы и возвращаемые значения. Тело часто выводится автоматически.
- Почините или осознанно заглушите то, что осталось (см. ниже про
type: ignore). - Когда модуль чист — включите для него строгие флаги через
overrides.
Локальное ужесточение вместо глобального
Не включайте disallow_untyped_defs на весь проект. Включайте на уже разобранные модули, чтобы они не деградировали обратно:
[[tool.mypy.overrides]]
# этот модуль уже полностью типизирован — держим планку
module = "app.core.money"
disallow_untyped_defs = true
warn_return_any = true
Заглушки для сторонних библиотек
Флаг ignore_missing_imports = true — костыль на старте: он молча пропускает любые пакеты без типов. Как только базовый шум убран, замените его точечными заглушками. Для многих библиотек есть пакеты со стабами: types-requests, types-PyYAML и другие из проекта typeshed. Поставьте нужные и снимите глобальный флаг, оставив исключения только там, где стабов действительно нет:
[[tool.mypy.overrides]]
module = "some_untyped_lib.*"
ignore_missing_imports = true
Точечное подавление: type: ignore
Комментарий # type: ignore без кода глушит все ошибки на строке — это слишком грубо. Всегда указывайте код ошибки, тогда новые проблемы на той же строке mypy всё равно поймает:
value = external_call() # type: ignore[no-any-return]
Чтобы такие заглушки не копились незаметно, включите проверку неиспользуемых игнорирований — mypy будет ругаться на комментарии, которые уже ничего не глушат:
[tool.mypy]
warn_unused_ignores = true
Сравнение стратегий подключения
| Подход | Плюс | Минус | Когда брать |
|---|---|---|---|
| Строгий режим сразу | Максимальная строгость с первого дня | Тысячи ошибок, проверку быстро отключают | Только маленький новый проект |
| Мягкий конфиг + overrides | Нулевой шум, CI работает сразу | Часть кода долго остаётся без типов | Большая живая база (этот материал) |
| Глушить построчно | Точечный контроль | Сотни ручных правок на старте | Средний проект с локальными проблемами |
Если не получилось
error: Cannot find implementation or library stub for module named '...'— пакет не установлен в текущем окружении или у него нет стабов. Установите пакет либо добавьте его вoverridesсignore_missing_imports.error: Skipping analyzing '...': module is installed, but missing library stubs— есть пакет со стабами (напримерtypes-...), поставьте его через pip.- mypy находит тысячи ошибок несмотря на мягкий конфиг — проверьте, что запускаете mypy из того же виртуального окружения, где стоят зависимости, и что он читает именно ваш
pyproject.toml(запустите с--config-file pyproject.toml). error: Duplicate module named '...'— mypy наткнулся на два файла с одинаковым именем модуля, обычно из-за отсутствия__init__.pyили лишних путей. Уточните область проверки, указав пакет явно.- CI падает на строке с
# type: ignore, которая раньше работала — приwarn_unused_ignores = trueэто значит, что ошибка исчезла и комментарий больше не нужен. Удалите его.
Что настроить после
Когда мягкий режим стоит в CI и не падает, двигайтесь к цели: доля модулей под строгими overrides растёт, блок ignore_errors сокращается. Хороший следующий шаг — включить --strict для новых директорий целиком, чтобы свежий код рождался типизированным. Отдельно посмотрите на отчёт покрытия типами: mypy --html-report покажет, какие модули ещё в тени. Автоматическая простановка аннотаций в существующий код (через отдельные инструменты вроде MonkeyType) — тема отдельного разбора, здесь важно было завести проверку без остановки разработки.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: mypy documentation — Running mypy and managing imports / configuration, mypy — Using mypy with an existing codebase
Частые вопросы
А если у меня mypy другой версии, например 1.8 или ещё старее?
Стоит ли включать strict сразу, если проект небольшой?
Нужно ли проставлять типы во всём коде, чтобы mypy приносил пользу?
Что делать, если mypy ругается на сторонний пакет без типов?
type: ignore без кода ошибки — это плохо?
Как встроить mypy в CI, чтобы он не падал на старом коде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.