Pull to refresh

Comments 93

Редко кого Гвидо ван Россум лично признает тупым... Впрочем лучшая реклама себя любимого для поиска работы) Статье заслуженный плюс

UPD: полез в PEP, а там ничего такого и нет)

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

Это мой хитрый план, чтобы пеп открыли ;) Продвигаю тайп хинты в массы через обман читателей

Есть ощущение, что дальше первого параграфа всё равно мало кто прочитает. Или даже будет Ctrl+F -> kesn

Ну в следующий раз ещё какой-нибудь конкурс придумаю :)

А так все по делу, что. Код без тайпхинтов надо запретить законодательно, а джунов оставлять наедине с mypy в strict режиме на месяц для начала разговора.

Очередной неадекватный последователь карго-культа тайпхинтинга

Это же классика, ещё Ленин предупреждал про цитаты из интернета

Так то цитаты а тут скриншот! Понимать надо.

PEP начинающийся словами "Чувак такой-то с Хабра реально глуп и не может разобраться в коде", неужели прикол не очевиден? )

Настолько, что надо проверить лично)

Хабр уже не торт, даже не положили python.org

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

В питоне нет перегрузки функций (overloading)

Есть singledispatch, но это не полноценная замена перегрузке:

note that the dispatch happens on the type of the first argument:

Выглядит отвратительно, лучше бы не было :)

Увы, что есть. В Python стандартная перегрузка (как в языках с ad-hoc полиморфизмом) принципиально невозможна.
А вообще, мне кажется, на любом языке можно писать плохо, хоть на Python, хоть на Java, хоть на C. Просто похой код на Java быстрее даст о себе знать, чем плохой код на Python. С этой точки зрения, к сожалению, простота Python играет с ним злую шутку. Многие просто забивают на то, чтобы разобраться, как сделать лучше исходя из принципа - "работает и ладно, что ещё от этого простого языка ждать"?

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

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

Помог сыну когда-то написать курсовую на Pyton (2 вечера изучал...) И сказал. нет уж, лучше вы к нам в c#

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

Я к тому, что выучив синтаксис за два дня, нельзя понять суть языка и иметь о нём хоть сколько-то объективное представление.

+ много

Я работал с кодом, когда-то переписанным механически один-к-одному с python на C++. Это ужас - взято худшее из обоих миров - многословность C++ и механически python словари переведены в условные TJsonValue

Филосовия - почти что любовь к совам)

Именно поэтому не люблю black - он пришёл из другого языка и откровенно враждебен Питону.

Прошу больше подробностей/ccылок про это, не гуглится.

Эм, даже не знаю, какие ссылки нужны.

Дело было так. Есть такой язык, Го называется. Там форматтер встроенный и довольно жёсткий, из разряда "должно выглядеть так и нииипёт!". И так это гошникам понравилось, что они решили такой подход ко всем ЯП применить. По сути, так, чтобы все ЯП выглядели однообразно. Но главная фишка Питона как раз в читаемости, как следствие ему просто не подходит всё это универсальное форматирование. Чтобы убедиться, что black выглядит ужасно, достаточно на with посмотреть. Да и эпопея с длиной строки в 79 (или 88, не помню уже) символов тоже доставляла. Сдались, ибо шквал был слишком большой.

достаточно на with посмотреть

Посмотрел, что ужасного мы должны там увидеть?

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

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

А самый читаемый вариант:

with (CtxManager1(),
       CtxManager2() as example):
    ...

С двойным отступом внутри блока. Скобочки с новой строки - не то.

Нечётное число пробелов в отступе, какая гадость... Я бы сказал, что это не black, а вы враждебны питону

Не знаю, где вы там нечётное число увидели. 8 шт и 4 шт. придирка ради придирки, как я понимаю.

Сделаю вид, что поверю, что вы опечатались, но это в любом случае менее читабельно чем вариант от mayorovp

Вот этот вариант лучше всего дружит с системами контроля версий:

with (
    CtxManager1() as example1,
    CtxManager2() as example2,
    CtxManager3() as example3,
):

Что же до читаемости - тут дело исключительно привычки. Как привыкните - так и будет читаемо.

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

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

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

with (
CtxManager1(),
CtxManager2() as example, # вот тут запятую надо
):

вот так надо, тогда форматтеры отстанут от этого куска

Статья очень классная. Но про assert лучше переделать. Например что их надо применять только в тестах. Потому как при упаковки кода в пакет (rpm/deb) код прекомпилируется с оптимизацией и assert'ы вырезаются.

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

вы же сами ворчите на антипаттерны, и вот assert не в тестах - как раз один из них. if + raise ведь тоже самое, не? в том числе в одну строку можно писать.

Ну да, можно if ...: raise AssertionError, согласен. В одну строчку линтер вряд ли даст написать, так что будет чуть длиннее.

UFO landed and left these words here

Голый return True LLMки часто в функции заталкивают.
А статья классная спасибо!

Статью одобряю, но

manifest_manager: ReadOnlyManifestManager | None = None

Вот здесь вы заставили всех писать if client.manifest_manager is not None: или assert client.manifest_manager is not None даже несмотря на то что условие всегда будет истинным

Согласен абсолютно. Увы, этот момент красиво разрешить не получилось, так как есть зависимость от других атрибутов - поэтому default_factory не прокатит. Все фиксы, что мне пришли в голову, были ужасны.

Тут в Питоне не хватает возможностей выразить зависимости.

Как вариант можно сделать свойство и в него всунуть assert.

А нельзя просто выключить ошибку?

@dataclass
class ShieldClient:
  manifest_manager: ReadOnlyManifestManager = None # type: ignore

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

Очень неизящно. Если был бы обычный (не дата) класс (или модуль!), атрибут вообще можно было бы не создавать до инициализации, оставив только аннотацию.

a: int
def init():
    global a
    a = 1
init()
print(a)



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

А вообще это самая бесячая особенность тайпхинтинга - необходимость бегать потом с assert is not None, если что то сразу проинициализировать нельзя, и приходится делать объявление Optional. Не хватает какой то выразительной конструкции вида "вот в этом контексте оно уже не Optional". Интересно, что в других языках подобной проблемы будто бы не существует. C Java понятно, там все смирились что NullPointerException может вывалиться в любой конструкции и просто не заморачиваются. А вот как эту проблему прям изящно решить непонятно. Тот же элвис-оператор (которого будем честны в питончике не хватает) - тоже тот еще костыль. Пытался раскурить зависимые типы но быстро понял - не моё :)

Типизация Питона сделана чтобы сказать "мы сделали", а реально сложные кейсы это не решает.

В C# кстати так можно:

using System.Diagnostics.CodeAnalysis;

class Example
{
    private int? _a;

    [MemberNotNull(nameof(_a))]
    public void Init()
    {
        _a = 1;
    }

    public void Print()
    {
        Init();
        Console.WriteLine(_a.Value); // Тут мы знаем _a is not null == true
    }
}

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

"Работает" - это слишком широкое понятие. Код, разбираемый в статье, тоже работает - с технической точки зрения. Проблемы тем не менее очевидны, как и в джанго. Кормит - ещё как! Вся армия бэкендеров при деле, всем задач найдётся всегда, никто не уйдёт обиженным и без зарплаты. Но достигается это за счёт того, что сначала кто-то пишет прототип за пять минут, а потом кто-то другой (или даже тот же самый, почему нет) тратит недели на то, чтобы внедрить в этот прототип одну мелкую деталь, которая отходит от очень обширного и универсального, но всё же прибитого гвоздями воркфлоу джанги/дрф. В то время как потратив на прототип буквально на полчаса больше, используя нормальный фреймворк, можно сократить время, требуемое на развитие проекта, в разы. Но, видимо, из-за низкого порога входа в язык, или не знаю уж из-за чего, половина веба выбрала есть кактусы, получая в результате армейский принцип "что бы разраб ни делал, главное чтобы затрахался" - как буквальную противоположность тому, для чего фреймворк задумывался.

Если это не проблема, то я тогда не знаю, что такое проблема.

Проблема - когда человек умер. Всё остальное - ситуации.

Исключения и ошибки

Возврат boolean (success / failure) или None вместо Exceptions

Скажу, что если сильно полагаться на исключения, то это нарушает флоу, в итоге код превращается в классическое спагетти, особенно если приправлен несколькими if-ами. В итоге имеем функцию строк на 20, из которах нас интересует только 3-я и 17-я, если это используется несколькими функциями, то читать это не лучше, чем базовые абстрактные классы. Доводилось видеть такое.

С другой стороны, сторонние библиотеки часто кидают ошибки. Ну и метод двойного презерватива - это нечто! Такого я ещё не видел.

Так что я придерживаюсь мнения, что по возможности надо использовать как можно меньше бросаний исключений. Либо их придётся продумывать. Короче, исключения - это сложно.

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

С конкретными исключениями проблема в том, что неизвестно, что изнутри функции может выскочить (помнится, в Java очень строго с этим, иногда реально не хватает). Вот кто сходу разберет, что какой-нибудь requests.get может выбросить? Не говоря уже о более вложенных библиотеках, где мб миллион различных исключений. Exception тут нормальный выбор

Хорошим тоном является создать базовый класс, от которого уже нужно наследоваться. Я по памяти не вспомню, что там у requests - надо в доку лезть, но уверен, что там что-то есть в таком роде. (Начинаю вспоминать, там вроде два больших класса ошибок: ClientError и ServerError). Так что надо понимать, что ловишь и ловить то, что надо. Exception надо ловить от отчаяния, когда не знаешь, что вообще поймать можешь.

Да, но не всегда. Особенно если какой-нибудь метод, который делает кучу разноплановых вещей (ну хотя бы запросить json, распарсить, извлечь данные, перепаковать, отправить). На любом этапе может вылезти самый разный error, а разницы, как правило, особой нет - сообщить об обломе и вывалиться/заснуть в ожидании повтора. Здесь кропотливое перечисление ловимых типов только добавляет шума, плюс нешуточная вероятность выпустить что-нибудь наружу (к примеру, добавили в середину еще какую-то обработку, а перечислить в except забыли). Зависит от ситуации, конечно, но не назвал бы ловлю exception безусловно плохой практикой. Тем более что язык не дает внятного средства получить список всех возможных типов исключений у метода (если, конечно, автор не жуткий аккуратист и не прописал их в docstring - но и тогда любая системная функция может выкинуть что-то неожиданное)

Смысл в том, что ты точно понимаешь, что тебя ждёт. А иначе получается как в присказке: если нет ТЗ, результат - ХЗ.

помнится, в Java очень строго с этим, иногда реально не хватает

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

А ситуация типа "пришло неизвестно что" может быть вообще где угодно: неизвестный код в errno от ядра, неизвестный код http response, не по схеме JSON... на каком-то уровне надо справляться с таким.

Python слишком мягкий и позволяет писать отвратительный код без ударов линейкой по пальцам программисту
Пишите на Rust
Питон не очень для больших проектов с долгом сроком сопровождения

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

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

Тут разработчик как бы говорит: я вот написал код для считывания количества подписчиков, но если он не работает, то это значит Дуров криво выводит значения, а у меня всё норм. Стоит телеграму поменять вывод с 1k на 1,234, и код будет говорить, что «сорян, данных нет».

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

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

Строже нужно быть только в случае, если для тебя эти данные критичны. А если это опциональная фича которая может не работать, и тебе от этого не больно, то плейсхолдить None - вполне себе норм стратегия, если ты заранее ОЖИДАЕШЬ, что такое может произойти, т.к. ты не отвечаешь за внешнего провайдера никак. Но, конечно, желательно еще и в логи куда то писать, мол: "отвалилось - чекни"

Главная проблема возврата None - в том, что результат выполнения придётся проверять на None, и эти проверки на None будут расплываться по стеку вверх до тех пор, пока не удастся обработать ситуацию как-то кроме возврата None. Плюс каждый возврат None - это потенциальный источник TypeError или AttributeError.

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

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

Рискну тут высказать об абстракциях и их преждевременном введении.
По сути своей абстракция - это возможность объектам различных классов демонстрировать одинаковое поведение в некоторых случаях.
Отсюда вопрос - откуда взялось равенство что объекты классов должны быть по сути ограничены этой самой абстракцией? Ведь таким образом - они обязаны становиться практически одинаковыми?
Абстракция даёт возможность применять одинаковые вызовы к различным классам. Но она - не должна обязывать классы выполнять только эти методы.

Есть такое понятие как: философская категория.
Проще объяснить на примере. Есть некое "Дерево". В смысле растение. Самого "дерева" никто в живую не видел. Зато есть "Яблоня", "Дуб", "Слива", "Орех" и так далее.

У всех них есть общие параметры. Которые мы, к слову - не придумываем, а выявляем, исследуя общие черты. То есть вначале мы должны прощупать и изучить множество "Слив", "Яблонь" - а потом выявив общие черты (а не придумав их изначально, и потом подгоняя под них результаты) мы можем создать для нашего удобства абстракцию "Дерево".

Но опять же - порочно думать что "Слива"="Дерево". Слива имеет признаки свойств дерева. Но вовсе не ограничено ими. Соответственно - отсюда вопрос. А почему вызовы должны происходить через абстракцию, а не через прямой класс?

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

Абстракция же не должна появляться До появления реализации. Это самокапкан. Соответственно - вовсе не обязательно и вызовы делать через абстракцию, а не через какой-то определенный класс.

К слову, у меня есть и аналогичные вопросы по примерам из статьи:

Какова ценность и смысл класса, где есть лишь 1 метод?
Какова ценность и смысл класса, где есть лишь 1 переменная?
Какова ценность и смысл абстракции, у которой 1 или меньше реализаций?

В чем проблема использовать и создавать абстракцию по принципу Just It Time? То есть именно когда она нужна? А не просто "везде и на будущее"?

Вы изобрели структурную типизацию.

В Python можно выразить через typing.Protocol.

Попробовал использовать питон через ИИ. Для простенькой задачи типа прочитать данные из базы,сделать транспонирование.и закинуть в csv. Код на простом запросе работал, но когда вместо запроса вставил хранимую процедуру то код не работал и сообщений об ошибках не было как и данных. Depseek на этом коде зациклился и начал выдавать варианты как на конвейере но ни один не работал Решил скормить этот код обычному браузерному ии и тот сразу же выдал одну строчку, которой не хватало. Оказывается что питоновский драйвер одбс не понимает вывода из хранимки без set no count on. Самое неприятное, что никаких сообщений или подсказок в питоне на этот счет не выводилось.

вот и вайбкодеры подъехали к обсуждению архитектурных недостатков Python

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

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

Комет кроме

Thank you ! We ❤️ you! — Krrish & Ishaan

Спасибо и вам, Криш и Ишан!

Вот успешный Криш. Он co-founder и CEO, выходец YCombinator W23: https://github.com/krrishdholakia Он получил куча денек и теперь шпарит 4000 contributions в год. 10x прогер!

Вот не менее успешный Ишан. Он co-founder и CTO: https://github.com/ishaan-jaff Он ></ярит 6700 contributions в год. Это 16.7x прогер.

Они нанимают себе в компанию за 80-180k USD в год + доля. А ты читаешь комментарии, небось еще из глубинки? А у них функция на 3200 строк, варнинг линтера отключен, и им не жмет :)

Ортогональна? А зачем тогда эта рубрика статей нужна? Зачем о качестве кода думать и его продвигать?

Это либо откладывание проблемы (нафигачим сейчас кое-как, а потом будем тратить часы на отладку и рефакторинг), либо перекладывание ответственности (нафигачим сейчас кое-как, а кто-то другой будет с этим разбираться). Ну типа это их путь, но не мой.

Сова-менеджер в технарном исполнении? Понятно-то, что подход (симптом) не новый. Самоучек и перепрофилировавшихся в 90-00 гг. можно было еще на это списать. Незнание подходов software engineering. Но тут уже невиданные ранее масштабы копрогенерации.

Какая поразительная в своей базовости статья

Ну так язык по дизайну тянет максимум на написание маленьких вспомогательных скриптов на пару экранов. Как BASIC. Как JavaScript.

Кто и зачем пытается строить из палочек и недоцемента огромные здания -- главный вопрос.

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

динамическая система типов

это приговор

Я бы не сказал, что приговор, но точно задел на будущие баги.

if item in arr был придуман в 1343 году, люди в 1342:

(это из шизофункции на 3к строк, там такого понаписано...)

Фантастика! Но я перестал писать на Python и перешёл на AutoHotkey из-за возможности работать с WinApi напрямую. Плюс язык pprototype-based. И вот тут вообще нет никаких типов, хорошо хоть под капотом один тип не преобразовывается тайно в другой, как в JavaScript:

dynamic(str) {
    if (str != '')
      str := Array()
    else
      str := false

    return str
}

dynamic(2) # empty arr
dynamic("aaaaaaa") # empty arr
dynamic("")  # boolean!

И нету констант нигде:

encapsulate() {
    static API_CONST := 0x008
    ...
    API_CONST := 12  # nah we'll just ignore it
}

Хотя бы проблемы вроде "прочитал новый паттерн ООП и внедрил его на уоовне 1970 года" тут случаются редко, потому что язык дает очень много возможностей написать все в число процедурном стиле. А классы использовать в основном без интерфейсов и абстрактных методов, просто потому что здесь нет типов (а следовательно костыли из интерфейсов не нужны)

Автор попал не в бровь, а в глаз. О наболевшем.
Внесу свою лепту.

1. Особенная боль — это ужасные названия функций/классов. Разработчики библиотек (в т.ч. стандартной) очень любят парадировать Си и сокращают названия функций, зажимая нижние подчёркивания / большие буквы, хотя с ними было бы гораздо лучше.
Пример из Pandas (тоже своего рода коллекция антипаттернов):

DataFrame.isna()  #Изна? А, 'is_NA()', сразу бы сказали...
DataFrame.nunique()  #Nunique? 'n_unique()'
DataFrame.idxmax()  #что простите? 'index_max()' или 'indexof_max'
DataFrame.cummin()  #без комментариев. 'cumul_min()'
DataFrame.astype()  #'as_type()'

#Зато в 'add_prefix()', 'sort_values()', 'to_json()' не поскупились.
# Двойные стандарты...


2. Или занятые удобные имена переменных (но это свойственно многим языкам, однако Питону в частности):

id, input, dir, zip
#Ну и ещё некоторые названия, удобные для одноразовых
# объектов соответствующего типа:
int, str, list, dict
#Больше никаких 'str: String' или 'list: List' в аргументах функций :(
#Опять же о двойных стандартах: Классы из ABC мы пишем с большой буквы,
# классы ошибок тоже с большой, а классы стандартных типов — нет?!

3. Или странные решения касательно системного дизайна / API (упоминалось в статье):

#(Кстати, опять тот же Pandas),
#если что, 'sum' применяется к каждому столбцу
DataFrame.sum()
DataFrame.apply(numpy.sum)
DataFrame.agg(numpy.sum)
DataFrame.agg("sum")  #название функции из numpy
#^^^ Вот к этой сигнатуре вопросы.
#Почему надо передавать строку с названием функции numpy, а не саму функцию?
# Будто бы написать 'np.sum' (да, именно 'np.') сильно сложнее чем '"sum"'
# И да, это всё варианты одного и того же действия — суммы каждого столбца.

Да, сокращения действительно удобны. Писать DataFrame.sum() гораздо проще, чем в худшем случае DataFrame.aggregate(numpy.sum). Но со строкой переборщили.

4. Больше операторов богине операторов!
Иногда операторы очень удачно "встают на места" и либо дают эмуляцию работы с числами / строками как надо, либо делают синтаксис более понятным (как std::cin >> data или std::cout << data в C++). Но не всегда:

df = DataFrame(...)
df ** 3  #Да, взяли и возвели таблицу в куб!
df.map(lambda x: x ** 3)  #Более понятный вариант
#Но не всё так просто! На самом деле, в этом примере операторы работают быстрее.
# (например, с умножением/делением). Потому что там используются специальные
# операции векторизации из 'numpy'.

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

5. Ну и непривычное разделение обязанностей (в стандартной библиотеке, например).
Где класс Path (путь к файлу) проверяет, существует ли файл, или удаляет этот файл (не поверите, методом unlink(), вместо привычного remove() / delete()). Зато copy() отсутствует, и для него нужна другая библиотека.

В принципе, к этим "особенностям" можно привыкнуть... Но после программирования на других языках, API многих библиотек (в т.ч. стандартной) Питона кажется странным и плохо спроектированным.

1 - странно винить язык, приводя в пример стороннюю библиотеку. Я бы скорее покритиковал неоднородность именований (get_ident и setprofile на соседних строках в threading). +1 за отсылку к Си. Я как-то из любопытства посмотрел на всё реально существующее семейство *printf и долго отходил от хтонического ужаса

2 - спорно. Можно писать i, s, lst в качестве имен, зато типы будут как можно короче, а не какой-нибудь System.Classes.StringList. Вот типы с заглавной, наверно, соглашусь.

3 - тоже сторонняя либа, как и 1. Но странные решения в самом деле есть

4 - тоже сторонняя либа, как и 1. В std [1]*2 == [1,1], а [1]**2 - не поддерживается. Но тем, кто крутит векторы и матрицы быстро умножить все элементы, наверно, крайне удобно

5 - Path преподносится как полная замена большинству файловых операций. Ну а unlink это из юникса

Я бы в список quirks добавил:

1 - слайс от bytes - это копия (после JS неожиданно)

2 - memoryview - может иметь размер элемента, и, например, memoryview от ctypes.Structure длиной в 1 элемент типа структуры. А перевести в байты можно только через memoryview.cast('B') (кому вообще нужен типизированный memoryview от единственной структуры?)

3 - memoryview1 == memoryview2 сравнивает по содержимому. НО - только если внутри байты, а если структуры - то по ссылке.

Сторонние библиотеки (тот же Pandas) не имеют отношения к языку. Но я их использовал лишь для яркой демонстрации примеров (так сказать, "из недавнего"). Но если поискать (а я, признаюсь, не очень на это настроен), в стандартной библиотеке можно найти всё то, в чём критиковался Pandas. Ибо это не просто проблема пары библиотек, а проблема официального, "основного" стиля Питона (и его стандарт.либы).
Кстати, есть и нормальные сторонние библиотеки (от того же гугла, например), у которых прям приятный API и naming conventions.

2. Сокращения рулят, ага. Но я, например, использую понятные без контекста имена переменных (например, matchIndex / index / str / id) и не жалею. И только в питоне у меня возникает проблема с названиями, ведь самые идеальные названия (краткость + понятность) заняты.

4. Что означает "массив умножить на два"? Это означает повторить его 2 раза. Очевидно ли это поведение? Не очень. Но применимо ко всем "повторяемым" типам (list/str/bytes/tuple). Оператор умножения, выполняющий повторение, мы так и быть отпустим (простим). Просто не вписывается в стандартную (привычную) картину мира, и приходится воспринимать каждый оператор не "буквально", а как какое-то смысловое действие (не "умножить число", а "повторить N раз"; или не "прибавить", а "создать обработчик событий" (прошу прощения на пример из C#) ). У каждого оператора появляется новое значение, зависящее от контекста.
Кстати, в том же Haskell, операторы не просто можно перегружать, а ещё и создавать свои собственные. Интересно спросить мнение хаскеллистов: как оно на практике? удобнее ли?

5. А что насчёт Path... Может, это какая-то инновация? :). Как модель акторов. Ведь по факту, путь к ресурсу означает и сам ресурс, так что мы можем им спокойно манипулировать... Но вы же ведь не пишете user_id.ban()? Вы, наверняка, используете ID для получения пользователя (самого ресурса), и только потом этим пользователем (ресурсом) как-то манипулируете: user = get_user(id); user.ban(). В прочем, и экспериментальный вариант с ID как самим ресурсом, тоже принимается. Эксперимент, так сказать.

А за дополнения насчёт bytes / memoryview — я лично туда не лазил, так что спасибо за вести оттуда)

Умножение массива в самом деле непривычный сахар, но имеет логику: `А*2 == А + А`, т.е. `[A]*2 == [A] + [A] == [A, A]` (сложение массивов же никого не удивляет?) С другой стороны, можно понять и `[A]*2 == [A*2]` (особенно удобно для матриц, одним оператором заменяем N вложенных циклов). Плохо, что эти два подхода существуют параллельно. Если первый - фича языка, второе лучше бы сделать через методы (тут уже пинок панде / нумпай).

Еще из фич языка мне до сих пор кажется корявым тернарный условный оператор. Имея Си-корни с довольно логичным `condition ? trueval : falseval` сложно смириться с хоть и более естественным для людей, но более беспорядочным `trueval if condition else falseval`

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

У разработчиков языка просто нет ясной картины и структуры в голове, поэтому как упало, так и прикрутили фичу. Очередная идея "упала" - тоже прибили наспех.

Хе хе, надо было учить пыху, там такого нет

А если серьезно, то 1\3 проблем, которая описана ранее была в пыхе, а сейчас с типизацией и станом на 10 уровне, хрен так напишешь, быть может когда нить в питон завезут функционал как в пыхе

2\3 описанного относится к почти всем языкам, а не конкретно к питону. Методы в 1 строку, наименование, переопределение, зависимости, одна реализация интерфейса это же только про питон?
Я понимаю автора, правда и то что он показал это ещё не самое худшее, что я видел.


Но, имхо, бугурт не стоит свеч, ясно же, что тот кто делал до тебя, никогда не работал с человеком, который задавался вопросом "А не д**б**б ли я?", что бы задаться таким же вопросом, но в сторону того, кто написал этот код

@dataclass(slots=True)
class ModelConfig:
env_file: str
extra: Literal["ignore", "forbid"] = "ignore"

class ShieldTestSettings(ShieldSettings):
model_config = ModelConfig(
env_file='env.test',
extra='ignore',
)

а у вас пеп торчит

Вот переводчик с шизофазийного языка на программистский:

  • «Нейрон» — это просто Node, то есть участник сети.


Я бы немного уточнил, что бывают случаи когда это разумно. DDD, ubiquitous language и все такое... Точно не всегда, не везде, но иногда шизофазийное - сильно экономит время.

Хорошая статья, нет правда, всегда радует, что кто-то думает схоже.

Попробую немного добавить по некоторым моментам.

Паттерн Фабрика - мне кажется он несколько недооценён в Питоне по причине того, что мы пытаемся использовать его слишком развёрнуто. Думаю, что имеет смысл добавить немного простоты, если фабрику оформить как функцию, в которую аргументом ты кидаешь класс описывающий конфигурацию и затем она собирает в единый объект всё приложение возвращая его, то получается весьма понятно и гибко. Конечно вероятность того, что нам понадобится миллион экземпляров приложения с чуть-чуть разными параметрами нет так много. Но всё же есть и эту вероятность закрывают тесты - так мы можем вариативно тестировать приложение. Например, банально отключая какую-то фичу и прогоняя весь набор тестов (несколько искусственный пример). Если кто использует pytest очень рекомендую фикстуры-фабрики.

Ошибки и исключения - Хочу добавить, что имеет смысл построить дерево исключений в самой верхушке которого будет что-то типа MySuperAppException(Exception), а все остальные исключения наследовать от него и давать логичные названия. И взять за правило, что эти два слова будут только в одном месте проекта. Тогда перехватить всё подряд будет значительно сложнее. Ещё repr(e)в некоторых ситуациях может быть вполне уместен в сообщениях.

Если вдруг по коду вырисовывается какая-то ситуация, ну которая вот точно никогда не произойдёт, такое иногда делают для некоторой завершённости или просто для ясности в будущем (я так иногда завершаю if...elif, добавляя ещё и else, чтоб мозг не выкипал) или если метод сделать на всякий случай (что вобщем-то чушь, есть абстрактные классы) то можно вставлять `raise NotImplementedError('Вразумительное сообщение')` чтобы вляпаться как следует и разобраться быстро.

А по поводу "вседозволенности" Python - мы все разумные люди и можем научиться брать эту его фичу под контроль. А если не можем, то есть много других хороших профессий. На разработке свет клином не сошёлся.

Sign up to leave a comment.

Information

Website
timeweb.cloud
Registered
Founded
Employees
201–500 employees
Location
Россия
Representative
Timeweb Cloud