Pull to refresh

Аннотировать или да?

Level of difficultyEasy
Reading time4 min
Views5.5K

Что такое аннотации типов в Python?

Читая эту статью надеюсь, что вы знакомы с аннотациями в Python. Но все же в вкратце напомню. Они нужны для того, чтобы придать некой строгости нашему динамически типизированному языку.

Утиная типизация
Утиная типизация

Как работает в IDE на примере PyCharm

Уже на этапе разработки мы можем видеть, что используем методы не так,  если их автор писал аннотации к коду.

Подсказки в PyCharm
Подсказки в PyCharm

В примере аргумент функции first ожидает числовое значение, а мы передаем строку. Благодаря современным редакторам кода мы можем избежать потенциальных ошибок.

А что под капотом?

В python аннотации типов читаются на этапе импорта и хранятся в атрибуте __annotations__. Посмотрим, что находится в этом атрибуте у нашего метода custom_sum:

Атрибут __annotations__
Атрибут __annotations__

В этом атрибуте хранится dict, ключу return которого соответствует аннотация возвращаемого типа, идущая после ->.

Для получения аннотаций typing предоставляет метод get_type_hints.

Использование метода get_type_hints
Использование метода get_type_hints

Как видно, значениями в словаре являются реальные классы python.

Накладные расходы?

К сожалению, добавление типов в код на python не дает никакого прироста производительности. Лишь вызывает некоторые накладные расходы:

  1. Импорт модулей занимает больше времени и памяти(так как нужно прочитать аннотации и записать их в атрибут __annotations__);

  2. Ссылка на типы, которые еще не определены требует использования строковых обозначений, а не реальных типов. 

С первым пунктом все понятно. Приведу пример, когда второй пункт может доставить нам сложности.

Пример класса, где метод возвращает экземпляр своего класса
Пример класса, где метод возвращает экземпляр своего класса

Можно видеть проблему “опережающей ссылки”.  Наш класс в одном из методов хочет вернуть экземпляр самого себя, но не сможет этого сделать, поскольку объект класса еще не определен, пока Python не закончит вычисление его тела. В этом случае мы вынуждены записать возвращаемое значение в виде строки. Из-за такого поведения аннотации обрабатывались в момент определения функций и модулей, что было вычислительно  затратно(программа должна понять, что эта строка означает).

Одобренный PEP 563 “Postponed Evaluation of Annotations” уменьшил время необходимое для обработки аннотаций типов во время выполнения. Аннотации типов больше не вычисляются в момент определения функции, вместо этого они сохраняются в аннотациях в строковой форме(не производя никаких вычислений). Чтобы достичь этого нужно сделать один import

Работа импорта
Работа импорта

Благодаря импорту from __future__ import annotations можно увидеть, что типы стали простыми строками, хотя не определены, как типы в кавычках. Изначально, планировалось сделать такое поведение поведением по умолчанию(не импортируя ничего). Но разработчики FastAPI и pydantic настояли на том, чтобы эта часть была опциональной так как она ломает работу этих библиотек.

MyPy

MyPy нужен для статической проверки типов в Python. Эта библиотека проверяет наши аннотации и как мы ими пользуемся. Приведу совсем небольшой пример, подробнее можно ознакомится по ссылке:

test_file.py для проверки MyPy
test_file.py для проверки MyPy

При вызове функции test_func мы ошибочно передаем вместо str тип int.  MyPy  помогает нам понять, что мы что-то делаем не так:

Ответ MyPy
Ответ MyPy

Интересные примеры аннотаций

Я не буду рассказывать про базовые типы, по типу str, int и тд. Так же опущу распространенное типы из библиотеки typing по типу List, Union, Optional так как об этом очень много информации.

Аннотирование *args **kwargs

Можно установить какие типы должен принимать *args **kwargs. В коде ниже указано, что args принимает только строковые значения, а kwargs только число. 

Пример type hints для 8agrs **kwargs
Пример type hints для 8agrs **kwargs

MyPy выдаст следующую ошибку:

Ошибка от MyPy
Ошибка от MyPy

Суть ошибки в том, что все неименованные аргументы *args  ждут тип str, мы же передаем int.

Объект является подтипом

Если  даны 2 класса User и ProUser,  который наследует User. В таком случае мы можем передавать методу, ожидающему User тип ProUser.

Пример работы с подтипами
Пример работы с подтипами
Ответ от MyPy
Ответ от MyPy

Вызываемые аргументы

Так же мы можем  аннотировать аргумент функции, даже если он является другой функцией, благодаря Callable  из модуля typing. С помощью этого инструмента можно так же указать, что ждет  функция и что она возвращает. В нашем случае метод bar принимает str и возвращает None.

Вызываемые аргументы
Вызываемые аргументы

Аннотирование переменных

До этого момента мы говорили лишь об аннотациях аргументов функций и возвращаемых значений. Помимо этого, аннотировать можно еще переменные.

Если говорить о себе - я не использую аннотации переменных. Так как типизация функций помогает нам работать с интерфейсами, тогда как работа  с переменными только увеличивает время разработки. Если есть большое желание указывать типы везде - стоит присмотреться к другому языку программирования.

Типизация для декораторов

Зачастую аннотировать декораторы - не самая хорошая идея. Они не только не улучшают чтение кода, а делают более непонятным происходящее. Приведу пример найденного аннотирования декоратора на просторах интернета:

Выглядит так себе, правда?
Выглядит так себе, правда?

Так что в итоге?

К сожалению, в Python типизация не дает никакого прироста производительности, а только потенциальное замедление и увеличение потребления памяти. Так же, чтобы корректно ее использовать - нужно потратить определенное время, чтобы разобраться что к чему. 

Если говорить о философии языка - он не про это, python больше про скорость разработки и простоту использования. Аннотация раскрывает себя во всей красе на больших проектах, когда нам важна общая надежность системы. Такие инструменты как MyPy подскажут нам на то, что мы делаем не так. Говоря о небольших проектах “на скорую руку” или скриптах  - я бы не стал использовать типизацию.

Из интересного можно подметить, что далеко не все core разработчики самого Python используют Type hints в своем коде.

Сам я уже не могу не использовать аннотации, для меня это вошло в привычку, для меня это как правило хорошего тона в разработке. Но своих коллег по работе принуждать к типам я не стану, а скорее просто делюсь преимуществами их использования.

Дорогой ценой я выучил урок: для небольших программ динамическая типизация - благо. Но для более крупных программ необходим более дисциплинированный подход. 

Гвидо Ван Россум, фанат Монти Пайтон

Полезные источники

Ромальо Л. PYTHON. К вершинам мастерства / 2-е издание / Москва: ДМК пресс, 2022. - 897

MyPy documentation - URL: https://mypy.readthedocs.io/en/stable/

PEP 484 – Type Hints - URL: https://peps.python.org/pep-0484/

PEP 483 – The Theory of Type Hints - URL: https://peps.python.org/pep-0483/

typing — Support for type hints - URL: https://docs.python.org/3/library/typing.html

Tags:
Hubs:
Total votes 7: ↑6 and ↓1+6
Comments58

Articles