Создатель Node.js Ryan Dahl впервые в Украине на конференции JS Fest. Программа уже на сайте

5 вещей, которые мне не нравятся в Python

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

Итак, 5 вещей, которые мне не нравятся в Python:

  • не-юникодные строки. Дихотомия str/unicode принесла всем разработчикам не-ascii приложений кучу головной боли. Остается ждать Python 3.0, где обещаны только unicode строки и отдельный тип byte для несимвольных данных (да-да, в Java так было с самого начала).
  • инструментальные средства. Вспомнив о Java, сразу вспоминаются мощные IDE типа Eclipse, реально ускоряющие разработку. Лучшая IDE для разработки на Python это, ИМХО, vim + ctags + grep. Связка не плохая, нет, но хотелось бы большего, чего-то из 21 века, а не из конца 70-х.
  • семантические пробелы (significant whitespace). К этому, конечно, привыкаешь, но все равно, я бы предпочел обычные пробелы, не несущие смысловой нагрузки. Не говоря о том, что в итоге мы остались с lambda-выражениями вместо полноценных анонимных функций. Плюс, массу времени можно было бы сэкономить на flame wars.
  • качество stdlib. Не все модули, входящие в Python standard library, одинаково хороши. Многие были включены чересчур поспешно и непродумано. Фиксация в stdlib означает как правило фиксацию API (иногда кривого) и невозможность избавиться от модуля в будущем.
  • GIL и работа на multicore окружении. Не очень ясно, насколько хорошо (или плохо) будет вести себя Python с повсеместным распространением multicode машин.
Пояснение: речь идет о критике инструмента, который я выбирал сам и использую совершенно сознательно. Любой, самый совершенный инструмент, неидеален. А некоторые даже утверждают, что если вы не видите недостатков значит просто не освоили его достаточно глубоко.
LinkedIn

36 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.
Товарищ koder привел мощную критику lazy_something, однако он глубко не вникал, поэтому она хоть и кажется правдоподобной., но таковой не всегда является.

1) lazy_import — идея неплохая но, к сожалению, очень слабо применимая, причем ее обязательно нужно делать «вручную», автоматически питон это не сделает.

Нет, не сделает. В самом питоне оно не встроено, поэтому не сделает. В проекте Базар ленивый импорт делается так:

from brzlib import lazy_importlazy_import.lazy_import("""from bzrlib import error, osutils""")Как видно из этого примера, добавляются 3 строчки-обвертки, а внутри используется обычный импорт-синтаксис питона.

а)Зачастую импорт идет не import something, а from something import x,y,zкак легко понять никакой lazy в таком случае не поможет.

Как показано в примере выше — помогает. Замечание мимо кассы.

б)Некоторые модули делают разные умные(или не очень ;) вещи при импорте, которые должныбыть сделаны именно в момент импорта.

Пример неудачный. Чем ленивый импорт в этом случае будет отличаться от импорта скажем внутри какой-то функции?

def foo(arg): import spam return spam.eggs(arg)

try: if dt == nnn: import XXXexcept: sys.stderr.write («Can’t load appropriate plugin for nnn exit.») sys.exit (1) В этом случае весь lazy выйдет полным боком т.к. для того что-бы определитьчто утилита не подходит для данного случая придется ее слегка поисполнятьи только потом Вы узнаете что она не подошла.

Делать sys.exit (1) только потому, что что-то не импортируется — это плохой стиль. Замечание опять же мимо кассы, потому что ленивый импорт следует применять тогда, когда он нужен, а не где попало.

в) Автоматическим lazy_import невозможно такое обработать (приведен упрощенный пример, все может быть гораздо хуже)

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

2) lazy_evaluated данные.Тут все сложнее, если сразу — то они автоматиски совсем никак не возможны, а неавтоматически Вы можете сделать их сами, только толк от них будет вкрайне редких случаях.

Удивительно, и для чего же в Питоне duck-typing??? Подменить в ран-тайме даже один класс другим можно, а вы вот так, с места в карьер. В вашем примере не хватает одной малости. Lazy данные так всегда и остаются lazy. И все время будет срабатывать getattr. Lazy должен быть временным промежуточным объектом, который подменяется на реальный при первом обращении. Например, смотрите в исходниках Базар модуль lazy_regexp, который позволяет откладывать компиляцию регулярного выражения до первого использования.Все остальное изложено вами верно, у ленивых вещей в питоне много ограничений, поскольку они реализуются искусственно, поэтому можно найти еще полмилиона причин почему они не будут работать в вашем коде. В коде Базар они работают и дают реальный выигрыш. Все остальное — спор о вкусе ананасов.

И на последок — всем советчикам по кардинальному улучшению питона.Оформите свои предложения на английском и запостите в python-dev

Да, это ценное предложение. Учитывая, что патчи для исправления ошибок в самом питоне (не добавление новой функциональности!) лежат годами на sf.net, а теперь в новом раундап трекере, то, да, кардинально улучшать питон бессмысленная затея. В проекте Базар никтоне ставит перед собой такую задачу. Нам нужно, чтобы Базар работал как задумано. Поэтому мы сами переписываем неустраивающие части питона под себя. Никто в python-dev никакие депеши не шлет.

А все таки GIL. Если запускать отдельный питоновский процесс, то сколько памяти съест каждый такой процесс?

Блин, пробелы в коде съелись (((. Но, имхо, для истинных питонеров, их восстановить не проблема.)

Всем любителям lazy_somethig посвящается:) 1) lazy_import — идея неплохая но, к сожалению, очень слабо применимая, причем ее обязательно нужно делать «вручную», автоматически питон это не сделает.Причины следующие: а) Зачастую импорт идет не import something, а from something import x, y, z как легко понять никакой lazy в таком случае не поможет. б) Некоторые модули делают разные умные (или не очень;) вещи при импорте, которые должны быть сделаны именно в момент импорта. Например модифицируют стек обработчиков urllib2, или добавляют onexit функции, устанавливают перехватчики исключений, etc. Отдельный прикол такое -...

try: if dt == nnn: import XXX except: sys.stderr.write("Can't load appropriate plugin for nnn exit.") sys.exit(1)

В этом случае весь lazy выйдет полным боком т.к. для того что-бы определить что утилита не подходит для данного случая придется ее слегка поисполнять и только потом Вы узнаете что она не подошла. в)Автоматическим lazy_import невозможно такое обработать (приведен упрощенный пример, все может быть гораздо хуже)

try: import XXX except ImportError: try: import YYY as XXX except ImportError: XXX = Default

Иначе говоря если модуль импортируется — он должен быть исполнен, поскольку компилятор не способен определить имеется ли внутри модуля зависимость от порядка загрузки и т.п.2) lazy_evaluated данные. Тут все сложнее, если сразу — то они автоматиски совсем никак не возможны, а неавтоматически Вы можете сделать их сами, только толк от них будет в крайне редких случаях. Код примерно такой:

class Lazy(object): def init(self,f,*dt,mp): self._func_ = [f,dt,mp] def getattr(self,name): try: return getattr(self.dict['_x_'],name) except KeyError: self._x_ = self._func_0,(self._func_[2])) return getattr(self.dict['_x_'],name) c = Lazy(lambda : []) print c.append print c.append(1)

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

import sys class Lazy(object): def init(self,f,*dt,mp): self._func_ = [f,dt,mp] def getattr(self,name): if '_x_' not in self.dict: self._x_ = self._func_0,(self._func_[2])) lk = sys.getframe(1).f_locals keys = list(_lk.keys()) for i in keys: if self is _lk[i]: break lk[i] = self.x_ return getattr(self.dict['_x_'],name) #Это тоже нужно основательно дописывать, но идея видна c = Lazy(lambda : []) print c print c.append print c print c.append(1)

Это гораздо быстрее, но имеет одну важную проблему (см. ниже). Но в обоих случаях остаются следующие грабли: 1) Невозможно перехватить обращение к специальным аттрибутам dict, slots, str, etc. А это широко используется т.н. pythonic кодом. 2) Такие объекты будут очень плохо дружить с C-кодом, точнее первый вариант кое-как, а второй вообще никак, т.к. он промодифицирует питоновский локальный скоп, что на C объекте никак не отразится. 3) Основная проблема — невозможность перехватить операции and, or, not, is + ф-ции type, isinstance, etc. Отдельный прикол — оператор =. Можно написать x = my_lazy_object y = my_lazy_object... Это все — дополнительные минусы в копилку второго варианта, т.к. каждый их x, y,... будет лезть в скоп наверх и там себя по разу тыкать. Других способов сейчас AFAIK быть не может. По поводу изменения компилятора с целью поддержки lazy_objects. Это так-же имеет вагон проблем, сходных с предыдущими + еще вагончик ( очень долго все описывать — не буду здесь) + две полные «бяки», а именно как бы Вы не внесли lazy в интерпретатор Вы замедлите работу с non-lazy объектами из-за необходимости из С-кода проверять «А не с lazy ли это мы сейчас работаем». Плюс куча проблем по отслеживанию, где же именно lazy нужно расчитать и подставить. Не стоит сравнивать питон с лиспом — он совсем другой и то что лиспу хорошо потону надо еще переварить. И на последок — всем советчикам по кардинальному улучшению питона. Оформите свои предложения на английском и запостите в python-dev рассылку, а мы посмеемся читая комментарии к этому посту;)). Или просто последите за python-dev с полгода и мысли типа «а я вот тут такую клевую штуку придумал — вот бы ее в питон засунуть, , а то они там дураки сами-то еще за 15 лет не дошли» будут основательно фильтроваться минимум недельным обдумыванием.

bialix, а для чего собственно Python недостаточно быстр? (Т.е. где же это lazy evaluation спасает ситуацию?)

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

bialix, а для чего собственно Python недостаточно быстр? (Т.е. где же это lazy evaluation спасает ситуацию?)

* нет iifhttp://lambda-the-ultimate.org...* нет встроенной поддержки lazy evaluation выражений* dict.setdefault: аргумент default должен быть lazy evaluated

Не так давно в комментах про обсуждение ФЯ некто указал, что мои вопросы про словари в Хаскеле сводятся к тривиальному «как писать на Хаскелле питон-программы». Здесь аналогичный случай, только наоборот.Однако, проблема эта есть. Питон недостаточно быстрый язык, и всякие ленивые штучки — это реальный ускоритель. Только они отсутствуют в ядре языка, и это не есть гуд. Приходится изобретать всякие костыли.Хотя, иногда костыли получаются очень даже ничего. Например, реализация ленивого импорта модулей: чтобы не импортировать сразу все, а только по мере надобности, это очень интересная вещь. Реализована (наверное первыми) в исходниках Mercurial, под названием demandload. Затем эту идею Джон Майнел из команды Bazaar перенес ее в базу кода bzr и попутно внес некоторые правильные изменения (модуль lazy_import). Так что (кому интересно) — можно и поглядеть.

> терпеть не могу _совершенноомерзительные_конвеншны__ с использованием знака подчеркиванияВарианты? Вполне нормальная штука, имхо. Никак не мешает и не напрягает.> необходимость указывать self в качестве первого параметра любого метода.Это как раз плюс. Или надо иметь какие-то два магических типа функций? Одни — которые функции, вторые — которые методы? А у меня вот сейчас есть функция, которая одновременно служит в некоторых местах функцией, а в некоторых классах — методом. Что ты предлагаешь взамен?

  • нет iif http://lambda-the-ultimate.org...* нет встроенной поддержки lazy evaluation выражений* dict.setdefault: аргумент default должен быть lazy evaluated

мне вот не нравитcя отсутсвие полиморфизма как аткового и невозможность операции присвоения в условиях

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

А я в Python терпеть не могу _совершенноомерзительные_конвеншны__ с использованием знака подчеркивания, отсутствие нормальной инкапсуляции и необходимость указывать self в качестве первого параметра любого метода.

многое из перечисленного на самом деле не проблемы питона. т.е. большую часть можно либо принять как фичу, либо списать как нечто, не относящееся к языку как к таковому. например, пресловутые пробелы это однозначно фича — без них питон не питон. отсутствие статической типизации — вещь полезная, но опять же, язык задумывался без нее, так что можно смириться. IDE, скорость, GIL — это все особенности окружения или конкретной реализации языка (пусть даже единственно кошерной реализации). аналогично, operator++ () не меняет язык принципиально — ну будет он чуть ближе к С, и что? А вот что действительно раздражает — так это когда фича есть, и вроде полезная, но сделана настолько криво, что каждый раз морщишься, когда ее используешь. те же однострочные лямбды, или эти подчеркивания и self этот идиотский. и что интересно, все привыкают, и пользуются, и ходят, как клиенты от Levine the Genius Language Designer. впрочем, справедливости ради должен заметить, что в других подобных языках (ruby, perl) таких горбов куда больше, так что Pythonic Way это все-таки скорее хорошо, чем плохо, как мне кажется.

отсутствие объявления локальных переменных, типа my в перле;

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

А отсутствие унарного оператора инкремента/декремента?: -) А что же таки мешает выбрать язык/технологию по задаче/вкусу? Нравится питон — пользуй питон. Хочешь статики и модных иде — возьми жабу с нетбинсом. Со тредами — туда же: -) И с удобными гуями. Нужна скорость — сиплюсы юзай. Полностью, или только для модуля — согласно Ответственному Решению.Бывает, правда, заказчик диктует, на чем надо писать... Но даже тут можно найти компромис: -) Навеяло нелюбовью к пробелам, созданию переменных по присвоению, тредами и жаждой к прочим «вкусностям» из соседних технологий/языков.

Ещё пара-тройка вещей: отсутствие статической типизации (пример, как сделано грамотно — boo); хотя, по-моему, это неспешно так рассматривается для python 3.0; отсутствие объявления локальных переменных, типа my в перле; вообще, не хватает поддержки статического анализа текста программы; отсутствие общего namespace для стандартных модулей, типаimport std.refrom std import cgi

str+unicode — почти не проблема: import sysreload (sys) sys.setdefaultencoding (’Cp1251’) После этого юникодные и обычные строки прозрачно преобразуются.Только вот почему хак с релоадом обязателен, никто мне не обьяснит?

Отступы в python это большой плюс, на мой взгляд.А как среду разработки можно попробовать Komodo.

Мои претензии: Создание переменной просто по присвоению. Вроде же практически все интерпретируемые языки прошли это и пришли к необходимости явного декларирования переменных, а тут такая деская болезнь. Это же касается создания атрибутов объекта по присвоению, хотя и не в такой мере: его в принципе можно решить частично решить через _getatts/setattr.Обращение к методу без () дает просто ссылку, которая в логинческом представлении дает true. Само по себе было бы терпимо если бы не каша даже в стандартных библиотеках: почему у файлового дескриптора isatty это метод, а closed это атрибут? Вроде еще что-то было, но уже забыл:)

И снова про IDE. Попробуй WingIDE. Платное, правда. Как-нить разживусь деньгами и может куплю. Но пока что лучшее, что видел для питона. Пробовал много всякого, кроме Eclipse+pydev — говорят тормозит порядочно, да и зависимостей слишком много emerge-ить:) Самая лучшая штука в это IDE, это её дебаг (брякпоинты, Step out/in/over). Недостаток — пока что однотредовый. Но в версии 3.0, которая ща в альфе, обещают multithread. Так же обещают автодополнение в debug-консоли. Это облегчит жизнь.Кстати, об автодополнении. В Wing оно просто великолепное.Вобщем, must have, как говорится.

По поводу IDE это ты загнул: посмотри на eric и удивись!

На Mac’е для питона хорош TextMate. Он вообще для всего хорош:)

Ну если в академических целях то можно наверное.Потом оформить критический по скорости функционал в отдельный модуль, и переписать на C:)

можно пример того что нельзя писать?

Например, ray tracing. Написать можно, а толку?:)

bialix можно пример того что нельзя писать?

Я пробовал практически все — не подошли по-разным причинам...Как-нибудь напишу статью что к чему:)

Отступы в питоне — это его плюс. В целом претензий к язку не имею вообще.

для екліпса є pydev, ну і крім нього ряд відкритих і комерційних IDE (Wings, BlackAdder, Eric, python режим в slickedit але він не настільки розвинутий як для С++)

А чем эклипс + pydev неустраивает в качестве иде?

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

ну і api stdlib зафіксований не намертво, нечасто але міняють. надіюсь на кращий в python 3000.

також згодний зі всім крім «significant whitespace», як на мене це невелика плата за більш читаємий код. А чому хочеться саме анонімності функцій? В пітоні і звичайну написати не складніше. (я серйозно, самому цікаво).

семантические пробелы (significant whitespace). К этому, конечно, привыкаешь, но все равно, я бы предпочел обычные пробелы, не несущие смысловой нагрузки.

С этим пунктом не согласен.

Иван: ссылку читал и не доверять Гвидо особых оснований нет. Но «осадок остался».;)

По поводу GIL Гвидо, насколько я понимаю, предлагает всем избавиться от необходимости делать многопроцессность через треды. Пишет, что треды популярны исключительно потому что их пропогандирует Java-сообщество и Windows-сообщество, где это действительно единственное нормальное средство. Но вот в юниксах он предлагает пользоваться процессами вместо тредов, потому что это проще.Вот, откопал ссылочку: http://mail.python.org/piperma...

Подписаться на комментарии