Типизация и mypy
Написал функцию, которая по подсказкам принимает два целых, и вызвал её с двумя строками. Рантайм спокойно вернул склеенную строку - ему всё равно. Прогнал тот же файл проверкой типов: три ошибки, включая обращение к методу у значения, которое может быть None. Одна и та же программа: интерпретатор молчит, проверка ругается.
Стержень: подсказки типов не проверяются во время работы, они инструмент отдельного анализатора и людей, читающих код.
// Формулировки: «проверяются ли аннотации в рантайме?», «зачем нужен mypy?», «что такое Optional и почему он всплывает везде?»
Что делают и чего не делают
Подсказки - это данные, приписанные к функции. Я их и достал: у функции есть словарь с типами аргументов и возврата. Интерпретатор их читает, хранит и НЕ проверяет - в этом весь смысл: язык остаётся динамическим, а проверка выносится наружу.
Проверку делает отдельный анализатор, который читает код и находит несоответствия ДО запуска. На моём примере он нашёл три ошибки, включая ту, что действительно упала бы в рантайме: обращение к методу у значения, которое может оказаться None. Заметь разницу: неверные типы аргументов рантайм проглотил молча, а вот None уронил программу - анализатор поймал оба случая заранее.
// Отсюда практическая ценность. Больше всего анализатор находит не «строка вместо числа», а именно случаи с None: функция может вернуть None, а вызывающий этого не ожидает. Это самая частая ошибка в динамическом коде, и она ловится ровно здесь, а не тестами.
- подсказка типа
- запись об ожидаемом типе; хранится как данные и в рантайме не проверяется
- статический анализатор
- читает код и находит несоответствия типов до запуска
Как внедрять в живой проект
Включить строгую проверку на большом проекте разом невозможно: она выдаст тысячи ошибок и её выключат в первый же горячий день. Рабочий порядок другой.
Начинают с мягкого режима на всём коде и с обязательной проверки только для НОВЫХ файлов. Потом включают строгость по модулям, начиная с ядра, где ошибки дороже всего. Отдельно добавляют пакеты с описаниями типов для внешних библиотек, иначе анализатор о них ничего не знает и молчит там, где мог бы помочь.
// И проверка обязана стоять в конвейере с блокировкой, иначе она превращается в украшение. Тот же приём, что и с другими проверками: зафиксировать текущее число ошибок как принятый долг и блокировать только рост. Это позволяет включить пользу сразу, а разбирать долг спокойно.
- мягкий режим
- проверка без строгих требований; точка входа для живого проекта
- описания типов библиотек
- отдельные пакеты, без них анализатор не знает про чужой код
Что описывать в первую очередь
Пользы больше всего от границ: публичные функции модулей, интерфейсы между слоями, данные, приходящие снаружи. Внутри короткой функции подсказки почти ничего не добавляют - там и так всё видно.
Отдельно стоит описывать структуры данных, которые ходят между частями системы. Тогда переименование поля или смена типа находится анализатором, а не в проде через неделю. Для этого берут классы данных или модели с проверкой значений - последние заодно проверяют данные в рантайме, что подсказки сами по себе не делают.
// И честная граница возможностей: анализатор не спасёт от неверной логики, не проверит данные, пришедшие из базы или по сети, и не заменит тесты. Он ловит ровно один класс ошибок - несостыковки типов, - зато делает это на всём коде и мгновенно.
- границы модулей
- публичные функции и интерфейсы между слоями; там подсказки полезнее всего
- модель с проверкой
- описание данных, которое проверяет значения и в рантайме
Как отвечать: «Проверяются ли аннотации типов во время выполнения?»
Нет. Они хранятся как данные - у функции есть словарь с типами аргументов и возврата, его можно прочитать, - но интерпретатор их не проверяет. Я специально показывал: функция, по подсказкам принимающая два целых, спокойно вернула склеенную строку при вызове с двумя строками. Проверку делает отдельный анализатор до запуска, и на том же файле он нашёл три ошибки. Самая ценная из них - обращение к методу у значения, которое может оказаться None: именно этот случай в рантайме и падает. Поэтому подсказки полезны в паре с анализатором в конвейере, а не сами по себе. Если нужна проверка данных именно в рантайме - например, того, что пришло по сети, - для этого берут модели с проверкой значений, потому что подсказки такого не делают.
Ответ отвечает прямо, показывает, где польза, и отделяет подсказки от проверки данных в рантайме. Последнее путают постоянно.
На чём валятся
- −Думают, что аннотации проверяются интерпретатором.
- −Ждут от анализатора проверки данных, пришедших извне.
- −Включают строгий режим разом на большом проекте и выключают его через неделю.
- −Не ставят проверку в конвейер, и она превращается в украшение.
- −Не добавляют описания типов для внешних библиотек и не понимают, почему проверка молчит.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 15, остальные разбираются в тренажёре.
- Что делает mypy?A)Статически проверяет типы по аннотациям до запуска кодаB)Ускоряет исполнение, компилируя аннотированный код в машинныйC)Автоматически расставляет аннотации типов по всему проекту самD)Проверяет типы в рантайме, останавливая программу на несоответствии
показать ответ и разбор
+A)Статически проверяет типы по аннотациям до запуска кода// разбор: mypy — статический анализатор: читает аннотации и код и находит несоответствия типов (передали str вместо int, забыли None-ветку) до запуска, не исполняя программу. Это ловит класс ошибок на этапе разработки/CI. Он ничего не ускоряет и не меняет поведение — только проверяет.
- Что означает аннотация
Optional[int]?A)Целое число, которое разрешено опустить только в середине кортежаB)Тип на выбор, аналог Any для гибкой типизации значенияC)Необязательный аргумент, который можно вообще не передавать в вызовD)Значение либо int, либо None — то же, что int | Noneпоказать ответ и разбор
+D)Значение либо int, либо None — то же, что int | None// разбор: Optional[int] — сокращение для int | None: значение либо целое, либо None. Это про тип, а не про обязательность аргумента (частая путаница): параметр с Optional-типом всё равно надо передать, если у него нет дефолта. mypy заставит обработать None-ветку, прежде чем использовать значение как int.
- Зачем вообще типизировать код на Python, если рантайм типы не проверяет?A)Аннотации ускоряют выполнение программы за счёт оптимизаций рантаймаB)Без аннотаций современный Python-код попросту не запускаетсяC)Ранняя ловля ошибок, подсказки IDE и живая документацияD)Типы обязательны только для асинхронного кода, для остального нет
показать ответ и разбор
+C)Ранняя ловля ошибок, подсказки IDE и живая документация// разбор: Типы окупаются вне рантайма: mypy ловит несоответствия в CI, IDE даёт автодополнение и находит опечатки, аннотации служат живой документацией сигнатур и делают рефакторинг безопаснее. На больших кодовых базах это заметно снижает класс глупых ошибок, ничего не стоя в исполнении.
- Аннотации типов (
x: int) — влияют ли они на выполнение программы?A)Да: аннотации ускоряют код, позволяя интерпретатору выделять память под точный типB)Да: несовпадение объявленного и фактического типа немедленно вызывает исключениеC)Да: интерпретатор проверяет соответствие типов при каждом присваивании значенияD)Нет: в рантайме они не проверяются, это подсказки для инструментов и людейпоказать ответ и разбор
+D)Нет: в рантайме они не проверяются, это подсказки для инструментов и людей// разбор: Python — язык с динамической типизацией; аннотации по умолчанию НЕ проверяются в рантайме и не влияют на выполнение. Их читают статические анализаторы (mypy, pyright), IDE (автодополнение, навигация) и люди (документация). Проверку включают отдельными инструментами до запуска. Рантайм-валидацию по аннотациям делают явно (Pydantic, typeguard), но это уже сторонние библиотеки.
- mypy подсветил ошибку типов, но программа всё равно запускается и работает. Почему?A)Значит, mypy ошибся: раз код запускается, то типы в нём заведомо корректныB)mypy — статическая проверка, она не блокирует запуск интерпретаторомC)Программа работает лишь по случайности и упадёт при первом же вызове этой функцииD)mypy обязан останавливать запуск при ошибке; раз не остановил — он настроен неверно
показать ответ и разбор
+B)mypy — статическая проверка, она не блокирует запуск интерпретатором// разбор: mypy проверяет типы статически, до и отдельно от исполнения; его вердикт не влияет на запуск — Python выполнит код, даже если mypy нашёл несоответствие. Ошибка типов может и не проявиться в рантайме (ветка не исполнилась), но это сигнал реального риска. Поэтому mypy ставят воротами в CI: красный отчёт не пускает merge, хотя интерпретатор код запустил бы.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.