Pull to refresh

Comments 58

По опыту, большинство аннотаций помогают тому же PyCharm с автокомплитом - что несомненно плюс.

Использование MyPy может найти пару ошибок в аннотациях (но и только!) - тут скорее минус, с учетом времени, которое можно потратить на попытки убедить MyPy в том, что ты правильно всё аннотировал.

Да, порой с MyPy приходится потанцевать, но мое мнение - он все же помогает повысить стабильность системы.

Ох.

Всё это "типовое" поветрие давно (ЕМНИП в 1993м году) уже под лупой исследовано Ларри Уоллом.

Когда Ларри разбирал призыв: "переходите на типы", то он наткнулся, что кроме вот этого мема никто ничего в аргументы не приводит:

Комментарий для зануд (конечно, Ларри рассматривал современный ему мем, речь всё-таки о 1993 годе).

Так вот, увидев, что НИКАКИХ других обоснований к призывам использовать типы у адептов типов нет, Ларри рассмотрел примеры из мема и сделал вывод:

проблема здесь состоит не в типах, а в неоднозначном применении оператора "плюс". В одном случае он означает "математическое сложение", а в другом - "конкатенация".

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

И нагрузки на программиста "размечай свой код, чтобы сделать компилятор счастливым" - тоже нет.

Интересная картинка, кажется та же проблема остается по сей день в JS?)

Хуже того. Она учащается и в TS частично перекочевать, так как операторы сравнений работают также, а это проблема не столько типов, сколько приведения типов

джаваскрипт с тех пор не реформировали

Речь-то в статье вообще про другое. В питоне и так типы есть, сложить строку с числом у вас не получится. Проблема в том, что тип становится известен только в процессе выполнения.

Поддерживать код, у которого не размечены типы - очень-очень больно.

Сейчас я работаю на проекте, где в одном из сервисов есть огромное число функций, которые принимают аргумент opts, или params, или оба этих аргумента одновременно. Эти аргументы являются словарями, которые формируются постепенно - то есть по мере того, как они пробрасываются из функции в функцию, они могут обогащаться новыми ключами.

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

А если бы в эти аргументы передавались датаклассы, и были бы проставлены соответствующие аннотации, то это полностью избавило бы меня от такой проблемы.

Речь-то в статье вообще про другое. В питоне и так типы есть, сложить строку с числом у вас не получится.

а сконкатенировать?

А если бы в эти аргументы передавались датаклассы, и были бы проставлены соответствующие аннотации,

ужас какой

разве аннотаций недостаточно?

>> разве аннотаций недостаточно?

Ну, если у вас словарь, то вы, конечно, можете написать аннотацию dict[string, Any], но толку тут не много, вы всё-равно не знаете, какие ключи в этом словаре. А в датаклассе полностью определён перечень полей и типы их значений.

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

В питоне под аннотациями понимают вполне конкретную вещь. В статье речь именно про питоновские аннотации.

>> а сконкатенировать?

А что вы вообще подразумеваете под конкатенацией строки и числа применительно к питону? И чем это отличается от сложения строки и числа?

под конкатенацией я подразумеваю конкатенацию

совершенно очевидно (и иных прочтений быть не может) что означает конкатенация:

конкатенация(1, 2)

конкатенация('1', '2')

конкатенация('ф', 'у')

конкатенация('ф', 2)

а под сложением я понимаю сложение

совершенно очевидно (и иных прочтений быть не может) что означает сложение:

сложение(1, 2)

сложение('1', '2')

сложение(1, '2')

И я говорю о проблеме в целом (об исследовании Ларри), а не о Python конкретно.

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

Хотя у Python есть опыт обратнонесовместимых шагов (Python2 -> 3), могли бы и нормально поправить дизайн.

вы упорно приводите примеры проблемы, которой нет в Python, вам упорно говорят, что тут подняли немного другую проблему

Как же проблемы в Python нет, если она есть, и именно на её решение и направлена вся эта возня с типами?

Ваш пример для ЯП со слабой типизацией, у python она строгая, а тут подняли проблему динамической типизации, это как бы совсем другое.

>> То, что Python криво задизайнили

А в чём именно вы видите кривой дизайн? В том, что в питоне нельзя по ошибке применить конкатенацию вместо сложения?

>> пытаются вылечить головную боль методом внедрения гильотин

А вот здесь вообще о чем речь? Ещё раз повторюсь - в питоне вообще нет никакой путаницы сложения и конкатенации. Поэтому в питоне никто не пытается решить эту проблему, за отсутствием проблемы. Аннотации используются вообще для другого.

>> И я говорю о проблеме в целом (об исследовании Ларри), а не о Python конкретно.

О какой проблеме-то? Во многих языках (включая питон) нет проблемы с неоднозначностью сложения/конкатенации, а польза от типов - есть.

И типы, внезапно, нужны не только для того, чтобы сделать счастливым компилятор. А ещё и для того, чтобы сделать работу программиста проще и сократить число ошибок.

И, кстати, почему вы упорно называете Ларри Уолла только по имени? Вы с ним лично знакомы?

А в чём именно вы видите кривой дизайн? В том, что в питоне нельзя по ошибке применить конкатенацию вместо сложения?

Именно что можно. Ведь оператора конкатенации в Python нет, там есть оператор "плюс", который выполняет и операцию сложения и операцию конкатенации.

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

и именно отсюда следует вся эта возня с типами в бестиповом языке.

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

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

ровно так и с операторами конкатенации и сложения

>> в бестиповом языке

Вот только в питоне строгая типизация.

И типы, внезапно, нужны не только для того, чтобы сделать счастливым
компилятор.

только для этого

А ещё и для того, чтобы сделать работу программиста проще и сократить число ошибок.

каким же образом эта работа становится проще, если вместо строки кода программист должен писать экраны?

Вы когда-нибудь занимались поддержкой проекта на 100000+ строк, в который два десятка программистов несколько лет писали?

Я бы посмотрел, как вы это будете без типов делать.

ага

занимался

а потом занимался проектами с типами

так вот с последними гемора куда больше

Расскажи в чем конкретно был гемор? Неправильно определили типы?

гемор в том, что вместо решения проблем проекта масса времени тратится на работу с типами

это как пытаться bash заменить на С++

на bash 4 строки

на С++ 4 модуля, плюс система сборки, компилятор итп

конечно bash vs C++ это предельный кейс, но именно он показывает никчемность, ненужность типов

Вы когда-нибудь занимались поддержкой проекта на 100000+

вы когда-нибудь задумывались, что цифра 100000+ может быть сокращена впятеро-десятеро простым отказом от типов?

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

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

Я не знаю, что у вас за специфичный проект такой был.

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

Ну и расстановка типов не увеличивает как-то драматически ни объём кода, ни затраченное время на его написание.Уж точно не в разы, даже если сказать, что на 10% - то это достаточно пессимистичная оценка.

И поэтому основную часть времени разработчик читает код, а не пишет.

Именно так. и потому за типы, за необоснованный ООП, за рекурсии, и за многое другое прямо бить по рукам иногда хочется.

>> за необоснованный ООП

За необоснованное что угодно можно бить по рукам.

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

ООП в 95% случаев оказывается необоснован

типы необоснованы в 100% случаев, где решение на скриптовом языке возможно

Поддерживать код, у которого не размечены типы - очень-очень больно.

поддерживать код, у которого они размечены, куда больнее!

ибо там где без типов 3 строки кода, с типами получается 3 экрана

ведь что Вы предлагаете?

вместо вызова функции сперва вызывать функции-конструкторы датасетов, которые станут аргументами функции

а эти конструкторы тоже через датасеты?

Бр-р-р!

В питоне для датаклассов обычно не используются какие-то отдельные функции конструкторы. А само по себе инстанцирование датакласса занимает примерно столько же места, сколько и создание словаря.

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

Мапы объектов можно и в типизированных языках передавать.

Если нет требования по производительности, просто делайте всё неизменяемым и создавайте обхект нового типа на каждной стадии. Если нет, то паттерн строитель.

Датаклассы не очень помогут, Any | None поменятеся на int | None и поля будут определены. Если вы не можете посчитать состояния объекта по пальцам одной руки, это уже сложно для восприятия. Два optional поля дают 4 варианта состояния. None(еще на заполнен) и None(Заплненное значение) это два разных стостояния, но из вообще не отличить.

С момента создания питона разработчики языков додумались, удивительное дело, в статически типизированных языках типа Kotlin выводить тип переменной из вида (написания) присваемого значения. Меньше синтаксического мусора, почти как в питоне).

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

Аннотирование функций в питоне подравнивает ситуацию по объёму писанины


Это в простых случаях. А в сложных в Питоне просто забиваешь и работает, а в статических языках придётся решать. А там это делать куда сложнее.

Сложнее и дольше - да. Но на действительно больших проектах это будет работать стабильнее)

А ничего, что это все пришло из C/C++, где можно перегружать функции почти произвольным образом? Правда поломать совсем всё не даст жёсткая типизация. Проблема вовсе не в том, что можно обсудить функции с разной сигнатурой, но одним именем. Котлин тут отстаёт. Проблема в слабой типизации. А у Python еще и в, что перегрузка сделана через такие же костыли, что и все остальные типы, и реализация таких функции ведёт к небходимости избегать строгой проверки типов и куче трудно уловимых ошибок.

У python всё-таки типизация сильная (нельзя сложить яблоки со столами), но динамическая. То есть до момента выполнения в общем случае вы не знаете точно пытаетесь ли складывать яблоки со столами

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

Именно поэтому предпочитаю писать на языках со статической типизацией, чтобы компилятор бил по рукам. Да и в python я бы предпочёл статическую типизацию, если бы она там была

Если бы она тут была - это был бы уже не python)

Безусловно, это был бы другой язык с которым было бы интересно поработать. Всё равно я продумываю данные и типы и мой прикладной python код скорее всего отлично бы ложился на статическую типизацию

Проблема в слабой типизации.


В Питоне сильная типизация. А еще она динамическая.

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

Файлы *.pyi решают проблему "потенциального замедления и увеличения потребления памяти" и можно писать аннотаций сколько хочешь.

Код картинками жгёт. Проверил, аннотации не вырезаются из оптимизированного кода, в отличии от __doc__ и assert.

# python3 -OOO file.py
def custom_sum(first: int, second: int) -> int:
    return first + second
    
print(custom_sum.__annotations__)

Спасибо про замечание с кодом с картинками. Это моя первая подобная статья и я вообще не подумал, что их можно вставлять в виде текста и так будет комфортнее читать.

Ну, логично, что аннотации не вырезаются.

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

Да, тот же FastApi завязан на них

Я воспринимаю аннотации типов скорее как декларативное описание "что в этом параметре/этой переменной". Условно, код на C-подобном языке float speed содержит описание типа, но не содержит сведения о том, как это значение интерпретировать - как метры в секунду или как километры в час. Тут ближе подходит концепция доменных типов, конечно - но аннотации в питоне являются приемлемым промежуточным решением, и позволяют добиться хоть какой-то ясности, не трогая саму логику.

Что касается вышеупомянутой проблемы со словарями - есть TypedDict, который позволяет, по сути, описать схему для словаря (по аналогии со схемой JSON).

" типы из библиотеки typing по типу List " - в более современных версиях питона list - встроенный тип и его не надо испортировать из typing, как и dict

Есть такое) С модулем typing, как по мне получился более выразительный пример. Хорошо, что не останавливался на этом вопросе, а решил его опустить

Кроме  коллкеций который переехали, там есть и другие вещи.

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

Если есть 3.9 и выше, а скорее всего так уже и есть, стоит использовать новые аннотации вместо устаревших.

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


Приветствую тебя гость из прошлого.

В 3.9 можно вот так:

from __future__ import annotations

https://peps.python.org/pep-0563/

Вы поспешили написать этот комментарий до того как прочли следующий абзац?

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

Да, только проверил дату публикации.

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

Посмотрел доку, а этот PEP до сих пор не включили в Питон.

Я его перепутал с https://peps.python.org/pep-0604/ которого мне в 3.9 не хватет.

Sign up to leave a comment.

Articles