Популярность подсказок типов в сообществе Python растет. Куда это нас приведет? Что мы можем сделать, чтобы использовать его правильно?

Все питонисты знают, что за пару лет подсказки типов в Python быстро набирают популярность. Это началось еще в Python 3.5, и с тех пор эта функциональность получает больше типов и функций, а в Python 3.10 — даже новый оператор |. К счастью или нет, этот оператор имеет то же значение, что и тот же оператор, используемый для множеств.

Я слышал разные мнения о подсказке шрифтов, от гнева и ненависти до любви и преданности. Большинство авторов, пишущих о подсказках типов в Python, считают их полезными, что повышает читабельность кода, особенно в случае большой базы кода. Однако при этом они обычно показывают очень простые примеры подсказок типов.

Хотя подсказки типов позволяют использовать статические средства проверки типов, мы не будем обсуждать эту тему в этой статье. Я просто признаю, что такая статическая проверка действительно может быть полезным инструментом. Здесь мы обсудим немного более спорную тему, а именно, полезны ли вообще подсказки типов. Кто-то воспринимает это как должное, но не я. Я хочу увидеть доказательство. Как и почти во всем, слишком много хорошего может быть плохим. Так ли это с подсказкой типа Python?

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

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

Теперь, с открытыми мыслями, давайте перейдем к обсуждению использования подсказок типов в Python, сосредоточив внимание на следующих вопросах: так ли они полезны, как мы постоянно слышим? Или, на самом деле, они могут снизить читаемость кода? Если это так, можем ли мы улучшить подсказки типов, чтобы результирующий код был более читабельным?

Вместо введения в подсказку типов

Я не буду объяснять подсказки типов, так как это сделало бы эту статью слишком длинной. Кроме того, они уже описаны в разных источниках, в том числе и здесь, на Medium (например, здесь, здесь или здесь). Во втором выпуске Fluent Python Лучано Рамальо дает довольно подробное введение. Вы можете увидеть подсказки в действии во многих книгах. Например, в книге Стивена Ф. Лотта Функциональное программирование на Python (также 2-е издание).

Подсказка типов служит трем основным целям: она позволяет запускать статические средства проверки вашего кода; это может помочь вам понять сигнатуры функций (типы аргументов, тип возвращаемого значения); и это может повысить читаемость кода. Рассмотрим пару примеров. Достаточно проанализировать только сигнатуры функций, куда включены подсказки типов.

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

Теперь поговорим об этих функциях:

  • parse_text(): Он принимает текст (в виде строки) и после его разбора каким-либо образом возвращает проанализированную строку. Конечно, мы понятия не имеем, что здесь означает этот синтаксический анализ, но это должно быть легко понять из тела функции. Эта подпись проста для понимания.
  • gdu(): Хм… Без понятия. Эта подпись - кошмар.
  • report_bug(): Очевидно, функция предназначена для сообщения об ошибке. Ошибка передается в виде строки. reporter, вероятно, кто-то/что-то сообщает об ошибке, заданной в виде строки со значением по умолчанию, предоставленным глобальной переменной DEFAULT_REPORTER. report_to — это место, куда следует сообщить об ошибке, также указанное в виде строки, также со значением по умолчанию, предоставляемым глобальной переменной (DEFAULT_REPORT_TO).
    Функция возвращает логическое значение; это, вероятно, сообщает, успешно ли было сообщено об ошибке. Несмотря на то, что они более сложны, чем подсказки типа parse_text(), они также довольно просты.
  • measure_distance(): Мы знаем, что функция измеряет расстояние и возвращает его в виде числа с плавающей запятой. Однако мы не знаем, какую единицу представляет этот поплавок. Расстояние от одного места до другого, и поскольку они представлены как кортежи из двух чисел с плавающей запятой, они, вероятно, являются географическими координатами. Эта подпись предполагает, что делает функция, но она могла бы быть более читабельной.

Конечно, мы редко можем узнать все о том, что делает функция, только прочитав ее сигнатуру. Но если сигнатура хорошая, мы можем многому научиться, даже не заглядывая в тело функции. Но когда такой бардак, как в gdu(), подсказка типов не только не помогает, но даже может быть дополнительной помехой, только увеличивая нашу головную боль.

В gdu(), конечно, проблема заключается не только в подсказках сложных типов; это кроется также — если не главным образом — в плохом именовании, которое совсем не помогает.

Есть способы улучшить читаемость. Посмотрите еще раз на measure_distance(). Его подпись не передает две вещи: какова единица измерения (километры, метры, мили или другие метрические единицы); и представляют ли эти два кортежа географические координаты. Можем ли мы улучшить подсказки типов, чтобы предоставить пользователю эти две части информации?

На самом деле, мы можем. Здесь могут очень помочь псевдонимы типов (которые я иногда называю пользовательскими типами):

Эта сигнатура говорит то, что мы пропустили раньше, и делает это благодаря псевдонимам типов Km и Coordinates: местоположения являются географическими координатами, а расстояние измеряется в километрах.

Часто бывает лучше использовать реальную структуру данных вместо псевдонима типа:

Здесь Coordinates — это структура данных namedtuple, а не псевдоним типа. Какой подход следует использовать? Это зависит. Поскольку и то, и другое одинаково легко понять, решите, будете ли вы использовать структуру данных также для разных целей или нет. Здесь он будет использоваться как контейнер реальных местоположений; в этом случае используйте пользовательскую структуру данных. В противном случае используйте псевдоним типа.

Помнить:

При создании псевдонимов типов именование имеет решающее значение. Используйте осмысленные имена, как если бы вы создавали пользовательскую структуру данных.

Итак, насколько полезными могут быть подсказки?

Подсказки типов могут помочь улучшить читаемость, как показали функции parse_text(), report_bug() и measure_distance(). Иногда подписи с информативными подсказками типа достаточно, чтобы читатель понял, что делает функция.

Извини, что? А, понятно… Забыл упомянуть функцию gdu() и полное отсутствие в ней чего-либо значимого. Вы правы, указывая на это как на контрпример. Давайте вспомним подпись gdu() и перепишем ее без подсказок типа:

Обе версии одинаково нечитаемы по одной причине: бессмысленные имена самой функции и ее аргументов. Эти подсказки типа не помогают объяснить, что делает функция, но могут усилить головную боль.

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

Как видите, типы сами по себе, без всего остального, тоже обычно бессмысленны. Мы знаем, что zizi — это callable, который принимает строку и возвращает строку, но это мало что значит. Отсюда наш первый важный вывод:

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

Даже при правильном именовании подсказки типов могут затруднить чтение и понимание сигнатуры функции. Посмотрите на следующий пример (исходник):

Отказ от ответственности: это пример подсказки типа в Python, и он не противоречит правилам. Я не хочу критиковать этот конкретный код, а хочу показать, что мне не нравится в некоторых случаях использования подсказок типов.

Теперь давайте полностью удалим подсказки типов:

Поскольку у нас нет подсказки типа для f, я изменил его имя на func, чтобы оно имело смысл. maybe_fmap() — это простая реализация монады Maybe. Сравните аннотированный и неаннотированный код. Считаете ли вы, что первое легче читать и понимать, чем второе? Я не; Я думаю, что их читабельность похожа.

Без подсказок типа легко увидеть, что maybe_fmap() принимает вызываемый объект в качестве аргумента; разве кто-нибудь не увидит, что func представляет вызываемый объект? Я вижу, что функция возвращает lambda , что действительно вызывается; трудно увидеть?

Я согласен, что у начинающих пользователей Python могут возникнуть проблемы с просмотром всего этого. Но как вы думаете, шрифтовые подсказки помогут им все это увидеть? Я не; Я думаю, что, несмотря на проблемы с пониманием вышеупомянутых аспектов maybe_fmap(), у них были бы еще большие проблемы с пониманием следующей подписи: maybe_fmap(f: Callable[a, b]) -> Callable[Optional[a], Optional[b]].

Этим примером я хочу подчеркнуть, что иногда подсказки типов делают код излишне сложным. Таким образом, в большинстве случаев я предпочитаю промежуточное решение, в котором подсказки типа предоставляются разумным (понятным) способом. Это означает, что нужно избегать слишком сложных подсказок типа.

Конечно, это вопрос предпочтений, но я боюсь работать с кодом, полным длинных и сложных подсказок типов. Я использую подсказки типов, но предпочитаю простые, как в некоторых примерах выше, например parse_text(), report_bug() и, прежде всего, measure_distance().

Подсказки сложных типов не облегчают понимание кода; на самом деле, они делают его еще менее читабельным. Вот почему меня не убеждают простые заявления о том, что подсказки типов улучшают читабельность кода Python. Я могу согласиться с тем, что понятные подсказки типов повышают читабельность кода. Однако когда дело доходит до практики, многие подсказки шрифтов, которые я видел, были далеки от понимания.

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

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

Прежде чем закончить, я хотел бы подчеркнуть, что мне нравится статическая типизация. Я использовал его в C и Go, а также в Cython. Во всех этих трех случаях мне нравилось использовать статическую типизацию, и я считал ее полезной. Но мы не должны забывать, что подсказка типа — это не статическая типизация; это две совершенно разные вещи и их не следует путать.

Заключение

Подсказка типов в Python уже не является новой концепцией, но она все еще развивается. Мое личное мнение таково, что, хотя подсказки не являются обязательными, они могут быть полезными, иногда весьма полезными. Но я боюсь того, что будет. Боюсь, что мне придется писать *args: Any, **kwargs: Any вместо *args, **kwargs,, хотя по определению *args, **kwargs означает, что они могут быть какими угодно. А я боюсь, потому что вижу все больше и больше непонятных намёков на шрифты.

В течение многих лет Python считался отличным языком благодаря понятному синтаксису. Он до сих пор заслуживает такого мнения? Как вы думаете, новички оценят удобочитаемость подсказок типа Python; например, из некоторых из приведенных выше примеров? Боюсь, скоро люди начнут замечать, что Python уже не так прост, как раз из-за подсказки типов, которая для новичка чаще является помехой, чем поддержкой.

Вот несколько правил, которые я использую, чтобы сделать подсказки типов понятными:

  1. Используйте подсказки необработанного типа, то есть определяйте их непосредственно внутри подписи, когда они просты и действительно понятны. Когда аргумент имеет действительно сложный тип, вы можете (i) упростить его подсказку типа (например, Callable вместо Callable[Tuple[str, str, int | float], Tuple[str, str]]), (ii) использовать подсказку псевдонима типа или (iii) создать пользовательскую структуру данных, которую вы можете использовать как подсказка типа. (Для (ii) и (iii) см. пункты 2 и 3 ниже.)
  2. Если вы можете использовать структуру данных (например, именованный кортеж, класс данных или класс) вместо соответствующей подсказки типа, делайте это. Это сводится к ситуациям, в которых вы действительно можете использовать эту структуру данных в своем коде.
  3. Всякий раз, когда вам нужна подсказка сложного типа, но не нужна соответствующая структура данных, определите соответствующий псевдоним типа (пользовательский тип). Сделайте это вне сигнатуры функции/метода и используйте осмысленное имя. Когда модуль или пакет содержит много псевдонимов типов, вы можете поместить их в специальный модуль (например, custom_types.py).
  4. Соблюдение вышеуказанных правил может сделать mypy несчастным. Как бы спорно это ни звучало, мне все равно. Читаемость кода для меня важнее, чем радость mypy.

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

Однако еще не поздно. Мы все можем что-то с этим сделать. Самое простое, что мы можем сделать, это разумно использовать подсказки типов. Если вы согласны со мной, с этого момента перепроверяйте все подсказки типов в коде Python и пересматривайте все, что выглядит неясным или двусмысленным.

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

Ресурсы