Comments 161
Спасибо, отличное правило для запоминания.
Не было ни одной итерации — частный случай невыполнения оператора break. Если бы else срабатывал только в случае "не было ни одной итерации", тогда было бы более понятно. По моему мнению, конечно. А так для использования оператора else обязательно должен быть оператор break, что неочевидно. Хотя допускаю, что это оценка с т.зр. С-подобных языков; вполне возможно, что те, кто пишет на python, в курсе такого поведения и им нормально.
finally
тут будет лучше» — отличный вопрос для собеседования. Если кандидат отвечате «да», то дальше с ним разговаривать не о чем, сразу отказ.Если ещё на тему
nobreak
можно спорить (лишнее ключевое слово — и тоже слегка загадочное), то у finally
есть чёткий смысл… и этот смысл сводится к тому, что обойти finally
нельзя. Никак.Следующий вопрос на собеседовании: как можно обойти finally.
Это «слегка» более серьёзные действия, чем использование
break
— согласитесь.Можно просто завершить процесс (несколькими способами) — но это не «обход
finally
», так как код, написанный после finally
при таком походе не исполняется.А у меня получилось:
def fin():
try:
while True:
try:
yield 'value'
except GeneratorExit as e:
print(e)
finally:
print('finally!')
it = fin()
next(it)
it = None
print('bam!')
Exception ignored in: <generator object fin at 0x7fc3a77e3750>
RuntimeError: generator ignored GeneratorExit
bam!
finally
— тупо _exit()
вызвать и всё.Во-первых вы всё равно полезли в «потроха», а во-вторых, главного, того, что ваш x67 считает нормальным, вот такого вот перехода, у вас нету:
try:
print("One")
... # Finally-ignore code here
finally:
print("Two")
print("Three")
# Finally-ignore code must skip "Two" but print "One" and "Three"
Это, не обход finally
, пока, извините.Хотя попытка хорошая. Может если ещё глубже закопаться в недра виртуальной машины, то удастся и обойти, как-нибудь,
finally
«по настоящему» — но в любом случае это всё в хаб «ненорамальное программирование», а не в прод…А нам тут предлагают код, который, иногда, игнорируется — согласно смыслу всей конструкции — засовывать в
finally
.вы берете на себя слишком большую ответственность додумывать за других.
Мой коммент был написан исключительно из моих соображений относительно перевода finally с английского языка, а не его смыслового значения в блоке try… finally в python
Впрочем, очень даже верное замечание про этот блок, ибо разная трактовка finally в различных конструкциях гораздо хуже неоднозначной трактовки else.
вы берете на себя слишком большую ответственность додумывать за других.Всё банальнее и проще: я не люблю пустых дискуссий.
Да, я знаю — в мире полно сайтов, где
Однако если у вас есть хотя бы зачатки умения мыслить логически — то вы бы должны понимать, что подобное поведение — превращает обсуждение «из спора в котором рождается истина» в бессмысленную трату времени и сил. Ну, действительно: если вы, «чукчаписатель», пишете не читая… и другие «чукчаписатели» делают то же самое… то… зачем всё это, чего вы хитите этим добиться? Поток сознания можно порождать бесконечно, если обратную связь можно не учитывать! Поэтому да: я (и не только я) хочу, чтобы комментарии читались — перед написанием своих.
Да, не всегда получается, много веток, оживлённая дискуссия… но это же явно не ваш случай, ведь так?
К счастью на Хабре собрались люди (специально ли их отбирали или так просто случайно получилось — неважно), которым идея «чукчаписателей» не нравится — и у них есть «ручки», позволяющие сделать так, что «чукчаписатели» — не слишком засоряли дискуссию.
Мой коммент был написан исключительно из моих соображений относительно перевода finally с английского языка, а не его смыслового значения в блоке try… finally в pythonА почему, собственно? Ваше сообщение было написано 17го числа. При этом вопрос
finally
подробно обсудили ещё в первый день: один раз… второй раз… третий…Ну сколько можно-то, в конце-концов? Я ещё понимаю ситуацию, когда пару часов у человека открыта вкладка и он не знает, что его предложение внес кто-то другой… но когда речь идёт о «позднем» комментарии дней так через пять… ну это банально невежливо по отношению к участникам дискуссии так делать.
Уважаемый khimЗнаете — вот как раз имитация обращений «чиновников высокого ранга» на Хабре не слишком котируется. То есть минусов за «особо вежливое» обращение вам не наставят, но и сильно лучше относиться не станут.
На Хабре уважают факты… одно из немногих мест, где эта «увядающая традиция» в эпоху «не рефлексирования, но распространения» ещё сохранилась.
for f in files:
if f.uuid == match_uuid:
break
else:
raise FileNotFound()
«если файл имеет нужный нам uuid, то закончить цикл, иначе вызвать исключение»,
а
«если хотя бы один из файлов имеет нужный нам uuid, то закончить цикл, иначе вызвать исключение», нет?
В си приходилось писать конструкции вида
int i;
for (i = 0; i < count; i++)
if (is_good(array[i]))
break;
if (i == count) {
printf("No good items\n");
return -1;
}
printf("Got good item %d\n", i);
// a lot of code to work with array[i]
После такого связка for-else (или while-else) из питона выглядит очень удобной.
i < count
, а при «нормальном» выходе из цикла значение i
имеет недопустимое (с точки зрения условия i < count
) значение.т.е. цикл
for(i = 0; i < count; i++) do_something();
можно переписать как:i = 0;
loop:
do_something();
i++;
if(i < count)
goto loop;
Так не получится потому что count может быть равен 0 (или даже меньше в случае косяка), в вашем примере цикл выполнится один раз как минимум при любом его значении.
for
на самом деле это:
i = 0;
while (i < count) {
do_something();
i++;
}
То есть i
по определению может быть равно count
исключительно в случае когда не было выхода из цикла по break
, ибо при выполнии break
до его инкремента просто не дойдёт.
i = 0;
loop:
if(i >= count)
goto loop_exit;
do_something();
i++;
goto loop;
loop exit:
//other code
А блок i==count выкинуть.
Правильно для понимания было бы либо вернуть ответ, если мы проверяем на наличие, либо вернуть элемент/элементы для обработки, если их надо обработать, либо взвести флаг.Кто такое правило ввёл? Цикл находит нужный нам элемент, дальше мы его обрабатываем. Кстати если не нашли — можем ведь и где-то ещё в другом месте поискать…
Да, часто можно выкрутится с флагами и прочим… но решение python — куда элегантнее…
Кто ввел правило разделять обработку и вывод результатов? А это важно? Оно слишком общеизвестно.
Кто ввел правило разделять обработку и вывод результатов? А это важно?Строго говоря нет. Но если это правило не введено законом — то его хорошо бы обосновать.
Оно слишком общеизвестно.В древности было «общеизвестно», что Земля плоская, стоит на трёх
Так что нет, не катит.
Да и речь идёт не о разделении двух шагов: они и так разделены и в Python — пустой строкой после
while
. Речь идёт о выносе в отдельную функцию — это-то вам зачем в 100% случаев?Но если это правило не введено законом — то его хорошо бы обосновать.
Тут вы полностью правы. Обоснование необходимо. Но ведь обоснование крайне легко нагуглить, разве нет?
Посмотрите "separation of concerns" Дейкстры.
В древности было «общеизвестно», что Земля плоская, стоит на трёх слонах, китах, животных, можно дойти до её края и упасть.
Общеизвестно — не значит верно. Это значит, что несколько неуместно выражать удивление и сразу начать возражать. Если бы вы просто спросили "а почему лучше отделить обработку и print?", это было бы более уместно, на мой скромный взгляд.
Конечно же, вы имеете право не согласиться с этим правилом, но всё же почитайте обоснования перед этим, правило-то хорошее.
это-то вам зачем в 100% случаев?
Я не понял, каким образом "правильно для понимания" от Fragster превратилось в "100% случаев" от вас.
Решение Python само по себе содержит скрытый флаг. Просто он не виден со стороны.
Мне оно нравится, но надо понимать, что оно весьма частично.
Ещё — пару раз встречалось, что в предполагаемом break слишком много общего кода и его таки надо вынести наружу (и тогда возникает тот же флаг, но в явном виде). Обдумывающему мне это, родилась мысль к else добавить then :)
Решение Python само по себе содержит скрытый флаг.Это в каком месте, извините?
Если вы уберёте слово
else
и замените break
на goto
— то python
-решение, внезапно, станет решением на C или C#… но флага в нём от этого не появится нигде.Ещё — пару раз встречалось, что в предполагаемом break слишком много общего кода и его таки надо вынести наружу (и тогда возникает тот же флаг, но в явном виде).Как и кому это поможет, извините? Либо вам нужен этот общий код всегда (в том числе и после прохождения по верке
else
) — тогда вам флаг и не нужен, либо не всегда — тогда этот код, извините, никак не «общий».Я считаю в варианте без goto. В варианте с goto таким флагом будет факт перехода. Но в Питоне нет goto, по крайней мере для программиста :)
> Как и кому это поможет, извините?
Если таких break несколько, и в них повторяется один и тот же код.
В си приходилось писать конструкции вида
Зачем? Просто создайте функцию:
Item find_good (Item[] array) {
for (int i = 0; i < array.length; i++)
if (is_good(array[i])) {
return array[i];
}
}
return null;
}
Теперь используете эту функцию:
Item good = find_good(array);
if (good == null) {
printf("Got good item: ", good);
} else {
printf("No good items\n");
}
Теперь у вас разделены поиск и вывод результатов, что является хорошей практикой.
T search (T[] array, Func<T, bool> callback) {
for (int i = 0; i < array.length; i++)
if (callback(array[i])) {
return array[i];
}
}
return null;
}
Теперь используете эту функцию:
Item good = search(array, is_good);
if (good == null) {
printf("Got good item: ", good);
} else {
printf("No good items\n");
}
И можете использовать её на любых массивах.
if (good == null) {
printf("Got good item: ", good);
} else {
printf("No good items\n");
}
Я жестко туплю или блоки условий действительно перепутаны местами?Ведь если выполняется условие good == null то у нас нет нужных элементов и надо показывать второй блок.
nullptr... код прост и понятен... только небыстр - ну это как всегда в python.
это, собственно, показывает почему подход python гораздо понятнее
Глупость, конечно. Вы бы могли настолько же притянуть и сказать, что это показывает божественный замысел и несостоятельность теории эволюции — было бы ровно столько же смысла.
Оно показывает, что я ошибся при копипасте. Точно так же я мог на питоне скопипастить и написать что-то такое:
for f in files:
if f.uuid != match_uuid:
break
else:
raise FileNotFound()
Получил бы ошибку и никакой «подход питона» никого от этого не спас бы. Просто потому что бездумно копипастить — плохо.
А то, что DaneSoul так просто обнаружил ошибку — показывает высокую читабельность моего примера
Это один из немногих случаев сравнительно оправданного использования goto
int i;
for (i = 0; i < count; i++)
if (is_good(array[i]))
goto item_found;
printf("No good items\n");
return -1;
item_found:
printf("Got good item %d\n", i);
// a lot of code to work with array[i]
Конечно, если похожих циклов рядом два, то можно перепутать метки. Ну так и i
тоже можно перепутать.
PS В прод я бы такое не писал, не поймут-с.
> Если вы не хотите ее использовать, может быт вы выбрали не тот язык?
Может быть. А может просто maintainer'ы Python добавили в язык очередной кривой костыль. Впрочем ничего нового (с)
Мне кажется что было бы лучше если бы такая фича стала мейнстримом
тогда она не будет ни странно смотреться, ни вызывать вопросы у тех кто пользуется другими языками.
Потому что задача, которую она решает, в реальности встречается и очень часто.
А почему бы тогда для этой фичи не придумать другое слово и не использовать else? :)
Мне ок (но кстати какое к примеру?). Но если вдруг будут добавлять в существующий язык то добавят else чтобы не добавлять новые ключевые слова (новые ключевые слова это проблема для обратной совместимости)
А else больше подошло-бы для варианты срабатывания после break (хотя кому такой вариант нужен?)
На мой взгляд было-бы логичнее поставить что-нить вроде finally. Слово не новое, известное, и логично передаёт, что мы завершаем цикл.Вы это специально или как? Во всех известных мне языках
finally
исполняется всегда — независимо от того, как мы пытаемся выйти из блока: break
, return
, да хоть goto
!Ибо
finally
— это таки finally
. Обязательная уборка. Аналог RAII. Большую диверсию, чем finally
, который можно обойти через break
— мне даже представить сложно.Я считаю, что это вредная фича, т.к. сбивает с толку людей, которые пользуются не только Python-ом но и другими языками.Ага. Давайте теперь пользоваться только однобуквенными массивами и целочисленными переменным двухбуквенными.
А то ж дискриминация против людей, привыкших к BASICу будет!
А может просто maintainer'ы Python добавили в язык очередной кривой костыль.И в чём же его кривизна? В том, что часто требуемая ситуация, когда нужно обработать отдельно случай, когда цикл искал-искал и ничего не нашёл может быть удобно и красиво записана? Без флагов, вынесени в отдельную функцию и прочих извращений?
ИМХО: кривизна в самом слове else. Для условия if код в else части выполняется, если не надо было выполнять код в if части. Что от него ожидать в случае с for — сложно догадаться. Наиболее логично, действительно, чтобы этот код выполнятся, если код в предыдущей части не выполнился ни разу(iterable оказался пустой).
Причём это питон, в котором "Explicit is better than implicit, Simple is better than complex", и вдруг такая неочевидная штука.
Назвали бы как-нибудь потипу last или finally — было бы понятно и удобно.
Рассмотрите не сам по себе
for
/else
, я всё цепочку. if testing_something:
# Handle true result...
else:
# Handle false result...
Понятно что делает? Вроде проблем нет. while testing_something:
# Handle true result and repeat...
else:
# Handle false result...
Понятно что делает? Ну… я затрудняюсь придумать какое-нибудь нестандартное поведение тут. try:
# Something dangerous.
except:
# Handle problems.
else:
# Uff... survived... let's celebrate.
Нет проблем? Вроде как всё очевидно. for x in y:
# Like while.
else:
# Like while, also.
А тут вдруг почему непонятно стало?Причём это питон, в котором «Explicit is better than implicit, Simple is better than complex», и вдруг такая неочевидная штука.Ну почему неочевидная-то? Если вы понимаете как
else
работает в if
, while
и try
… то почему for
стал камнем преткновения?Он там точно также работает, как и везде, во всех остальных случаях…
"А тут вдруг почему непонятно стало?"
Потому что без break работать не будет. Добавьте break — и это уже меньше похоже на примеры и if, while и прочим
Кстати, поведение в примере с while отличается от поведения в примере из статьи. В случае с while у вас вызовется либо первый блок, либо второй, но не оба сразу, в статье же могут быть выполнены оба блока.
При количестве итераций == N, в цикле while выполнится N раз первый блок и один раз второй. В цикле for выполнится N раз первый блок и один раз второй.
Где отличие?
Правильно ли я понимаю, что и в while тоже должен быть оператор break? Иначе смысла в блоке else я не вижу.
Вот более наглядный пример работы с break и без него: habr.com/ru/post/510426/#comment_21834526
В else может быть например вывод какой-то суммарной информации по завершению цикла (счетчик итераций, итоговая сумма и т.п.) в штатном режиме без срабатывания/присутствия break.
Получается если нет break, то else слово тоже бессмысленно, можно просто код следом воткнуть, разве нет?
check_list = ['1', '2', '3', 'error']
list_sum = 0
for i in check_list:
if i.isnumeric():
list_sum += int(i)
else:
print("Wrong Data!")
break
else:
print(list_sum)
Сумма напечатается если обработало без break, если же он был, то не напечатается. Если просто код следом воткнуть, то надо вводить дополнительный флаг, который менять перед break и проверять после цикла перед выводом значения.
Не должен, но может быть.
Этот блок else выполнится и для цикла for, и для цикла while если сам цикл не был прерван.
В случае с while у вас вызовется либо первый блок, либо второй, но не оба сразу
Наоборот. Оба сразу и вызовутся. Не самая очевидная штука.
try… except… else
Нет проблем? Вроде как всё очевидно.
Не очевидно. В предыдущих двух кейсах else относился к логическому условию, а тут никакого явного условия нет. Учитывая, что там ещё finally встречается, я бы назвал третий блок как-нибудь в духе noexcept.
for x in y… else
А тут вдруг почему непонятно стало?
По той же причине — else должен относиться к логическому условию, которого тут нет.
Конечно, это можно запомнить. Но это именно неочевидный момент, который надо специально заучивать, очень не по-питоновски.
- The for… else construct was devised by Donald Knuth as a replacement for certain GOTO use cases;
- Reusing the else keyword made sense because «it's what Knuth used, and people knew, at that time, all [for statements] had embedded an if and GOTO underneath, and they expected the else;»
- In hindsight, it should have been called «no break» (or possibly «nobreak»), and then it wouldn't be confusing.*
stackoverflow.com/a/23748240
Вообщем «так исторически сложилось», а добавлять новое ключевое слово типа «nobreak» они не хотят :)
homm, не хотите добавить этот комментарий в статью? Интересный, и понятно откуда взялся такой странный синтаксис.
Для какого-нибудь
while(a < 10):
это само условное выражение a<10,а внутри
for f in files:
— это условие «есть ли еще файлы в списке files? — нет, тогда выполняем то, что в блоке else после цикла». Для try/Есть куда более странные фичи в python, которых, почему-то, не избегают. Чего один тернатный оператор
ha if foo() else hi
стоит…Но вспоминая этот замысел сейчас, я задаюсь таким вопросом: «else должен сработать только однажды — если тело цикла ни разу не выполнялось?». По-моему, такой была моя первоначальная идея. А можно считать, что раз речь про цикл, то в любом случае, как только условие стало ложным, независимо от кол-ва итераций. Иными словами, и при окончании цикла — тоже. В Python используется этот второй вариант.
В общем, несмотря на то, что else иногда позволяет уменьшить copy-paste (например не дублировать условие), тут есть неочевидные моменты. Более того, может иметь право на существование и 3-й вариант (гипотетически): else выполняется именно тогда, когда условие было истинным, а потом стало ложным. То есть, только в том случае, если какое-то кол-во итераций уже было выполнено, а если их не было вовсе — нет.
Вариант, который принят в Python, наиболее, пожалуй, логичный, но самый ли он удобный, сказать не возьмусь — нужно было бы проанализировать огромную кучу кода, и выбрать, какая из версий этой идиомы оптимальней. Пока же, по косвенным признакам можно заключить, что востребованность у нее весьма низкая.
Поскольку в этом случае начинает выполняться то, что за циклом, достаточно записать действия сразу за циклом, и else не нужен.
> Вариант, который принят в Python, наиболее, пожалуй, логичный, но самый ли он удобный
Его удобство видится именно тогда, когда вводишь дополнительную переменную «а не случился ли нужный нам break».
«Поскольку программа выполняется последовательно нужно просто записывать одну строку под другой» Гениально, браво!
А то, что «за циклом» можно оказаться как побывав в нем, так и не побывав — ни на какие мысли не наводит? А вот для логики программы, путь, который привел к этой самой точке «за циклом», может быть существенным, именно потому-то там else, а не просто «пустая строка, пишите код дальше!»
Вот тут-то, например, наличие того же break (из хотя бы раз начатого выполняться цикла), который досрочно прерывает цикл и сказывается — «break отменяет else».
> А вот для логики программы, путь, который привел к этой самой точке «за циклом», может быть существенным
Наверно, тут нужно собирать статистику вариантов важности ситуации, типа:
1) Нам не нужно вообще что-то делать, если цикл не выполнился ни разу или break не вызван, всё уже сделано. (Банально и даёт подавляющее большинство, не интересно.)
2) Нам нужно реагировать на то, что break не вызван, но не важно, была ли хоть одна итерация при этом или нет. (То, на что заточен else сейчас в Python.)
3) Нам нужно реагировать на то, что было 0 итераций, но не важно, вышли мы по break или нет.
4) Что-то ещё более сложное. (Бывает, и нередко.)
На любое введение хитрой конструкции в языке обычно требуются обоснования для реально большого количества вариантов применения. Я правильно вас понял, вы агитируете за (3)? И при этом нельзя заранее проверить, будет ли выполнение? Если да, вопрос — что у вас за специфика, что это настолько важно, что хочется отдельной конструкции? А то моя практика показывает, что (2) достаточно частый случай, а (3) — ну честно не помню.
> …
> Я правильно вас понял, вы агитируете за (3)?
могу только процитировать свой ранее данный ответ:
Вариант, который принят в Python, наиболее, пожалуй, логичный, но самый ли он удобный, сказать не возьмусь — нужно было бы проанализировать огромную кучу кода, и выбрать, какая из версий этой идиомы оптимальней. Пока же, по косвенным признакам можно заключить, что востребованность у нее весьма низкая.
Что это за «косвенные признаки» у вас — я не знаю, но я неоднократно использовал такую конструкцию. По-нормальному таки надо было бы набрать статистику, но у меня на это не скоро будет время.
Блок else после циклов относится не к самому циклу, а к оператору break!— Как раз неудачная формулировка, которую не нужно запоминать, потому что «относится» никак никому не поможет. Более того — она ошибочна, потому что даже если break вообще указан не будет, else вполне может использоваться — потому что else «относится» к циклу, а не к «побегу» из него.
Беглый поиск по англоязычному интернету находит:
«The else block just after for/while is executed only when the loop is NOT terminated by a break statement.»,
что можно перевести как
«else-блок for/while выполняется только тогда, когда цикл НЕ прерван break'ом»,
или так:
«else-блок for/while не выполняется при досрочном (break) окончании»,
или «break отменяет else».
Вот последнее сильно проще, понятнее, и да, можно и запомнить.
Ну да. А в конструкции-прародителе — if отменяет else. Или, как говорят чаще — else относится к if.
— Как раз неудачная формулировка, которую не нужно запоминать, потому что «относится» никак никому не поможет. Более того — она ошибочна, потому что даже если break вообще указан не будет, else вполне может использоваться — потому что else «относится» к циклу, а не к «побегу» из него.
+++++
8.2. The while statement
The while statement is used for repeated execution as long as an expression is true:
while_stmt ::= "while" expression ":" suite
["else" ":" suite]
This repeatedly tests the expression and, if it is true, executes the first suite; if the expression is false (which may be the first time it is tested) the suite of the else clause, if present, is executed and the loop terminates.
A break statement executed in the first suite terminates the loop without executing the else clause’s suite. A continue statement executed in the first suite skips the rest of the suite and goes back to testing the expression.
Команда while используется для повторяющихся вычислений, пока выражение истинно.
Команда while ::= "while" условное выражение ":" блок команд
["else" ":" блок команд]
При каждом проходе цикл проверяет условное выражение и, если истинно, выполняет первый блок команд. Если условное выражение ложно (что может случится, в том числе, и на первом проходе), то выполняется, если имеется, блок команд else, и цикл завершается.
Команда break в первом блоке команд завершает цикл без выполнения блока команд else.
Команда continue в первом блоке команд пропускает остаток блока и возвращает цикл к выполнению условного выражения.
Соответственно, логика вполне воспринимаемая. Код внутри цикла исполняется, когда условное выражение истинно, может быть несколько раз или ни одного. Код под оператором else исполняется, когда условное выражение ложно — один раз.
Оператор break, самый, кстати, здесь не питонистый, на мой взгляд, обрубает цикл «на полуслове» и выносит исполнение кода за пределы цикла. К else он вообще никак не относится, он просто прерывает всю конструкцию.
Поэтому тут вместо else никак не подойдет finally — finally исполняется всегда, было исключение или не было
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
for n in range(2, 20):
print("\nИщем делители числа "+str(n)+" ...")
for x in range(2, n):
print("... проверяем делится ли число на "+str(x))
if n % x == 0:
# делитель для числа х найден в диапазоне (2,n)
print str(n)+' = '+str(x)+' * '+str(n/x)
# дальше можно не искать
break
else:
print str(n)+' - простое число'
~~~~~~~~~~~~~~~~~~~~~~~~
Ищем делители числа 7…
… проверяем делится ли число на 2
… проверяем делится ли число на 3
… проверяем делится ли число на 4
… проверяем делится ли число на 5
… проверяем делится ли число на 6
7 — простое число
Ищем делители числа 8…
… проверяем делится ли число на 2
8 = 2 * 4
~~~~~~~~~~~~~~~~~~~~~~~~
Это однозначно неочевидно, но однозначно полезно. Т.е. если цикл был завершен досрочно, то это одна информация, а если не был, то это другая информация.
Я навскидку не смог добиться такого же результата классическим способом. Насчет того что слово else неудачно выбрано — наверное, да, отчасти именно это добавляет «неочевидности». Можно было бы, имхо, взять слово then — типа, «затем», т.е. после того как цикл был пройден полностью.
Уверен, что на питоне это можно выразить без такой неочевидной конструкции не менее лаконично и понятно, чем на плюсах
// C++20 std::ranges library
namespace rv = std::ranges::views;
for (int n : rv::iota(2, 20)) {
std::cout << "\nИщем делители числа " << n << " ...\n";
if (std::ranges::none_of(
rv::iota(2, n),
[n] (int x) {
std::cout << "... проверяем делится ли число на " << x << "\n";
if (n % x == 0)
std::cout << n << " = " << x << " * " << n/x << "\n";
return n % x == 0;
}))
std::cout << n << " - простое число\n";
}
Что очень хорошо видно потому, что в Python можно заметить, что вы были слегка идиотом, заменить одну строку
for x in range(2, n):
на две: x = 2
и while x * x < n:
— и получить заметное ускорение… а теперь попробуйте то же самое с вашими йотами изобразить (использовать sqrt
, пожалуйста, не предлагать — это изменение в логике… мы же тут управляющие конструкции обсуждаем, а не математику).Во-первых, вы инкремент забыли, так что строчек будет три. Во-вторых, x*x
— это такое же изменение в логике, как и sqrt(n)
.
Во-вторых, x*x — это такое же изменение в логике, как и sqrt(n).Как раз
x*x
— это изменение в логике. А sqrt(n)
— нет.И если в Python логику можно изменить просто и быстро, то в C++ — вы этого сделать не можете,
ranges
это не поддерживают.Зачем вместо простого и понятного кода на универсальном ЯП писать костыли на другом, неуниверсальном, да ещё и пропогандировать это — мне, видимо, не понять.
Я навскидку не смог добиться такого же результата классическим способомПсевдокод. Как по мне — сильно читабельнее варианта с for-else
for n in range(2, 20):
divisors = get_divisors(n)
if divisors.length == 1:
print `${n} - простое число`
else:
print `${n} = ${divisors.join(' * ')}`
Ну уж извините, что преувеличил ваши интеллектуальные способности.
Или вынести часть кода в функцию — это «совсем другой алгоритм»? Ну тогда почитайте, что такое алгоритм.

На питоне, увы, не пишу.
function getDivisors (n) {
for (var x = 2; x < n; x++) {
if (n % x == 0) {
return [x, n/x];
}
}
return [n];
}
for (var n = 2; n < 20; n++) {
var divisors = getDivisors(n);
if (divisors.length == 1) {
console.log(`${n} - простое число`);
} else {
console.log(`${n} = ${divisors.join(' * ')}`);
}
}
И довольно просто сделать, чтобы раскладывало на все делители:

А как такое сделать на том коде на питоне?
Одно непонятно, как вы собрались с помощью getDivisors избавляться от цикла for по range, если внутри getDivisors именно цикл у вас и написан.
Вы понимаете или нет, что заменить for на while можно было и не вводя лишнюю функцию, да ещё и такую странную?
по сути, просто повторяет внутренний цикл предыдущего оратора
А чем функция странная?
А, я ветку перепутал. Да, тут замена цикла не обсуждалась.
Функция странная тем, что она возвращает bool, закодированный в длине массива. Я не вижу у нее других применений кроме как проверить длину массива.
Но не все, а только минимальный и максимальный несобственные.
При этом для простых чисел она почему-то возвращает максимальный собственный...
В том то и дело, что если делать «дедовским способом» как в Вашем примере, то это вызов функции, хранение списка результатов, дополнительное ветвление, а этa «странная конструкция» в питоне делает все без лишних манипуляций.
И глядя на этот контраст, здоровый минимализм который показывает в этом моменте питон — завораживает. Решение выдается в тот момент когда решение готово, и не шагом позже. На этом фоне все остальное смотрится как расточительство. Если приложить к этому философию бережливого производства (Lean), то это потери хранения, транспортировки, ожидания, излишней обработки.
Возможно что-то типа finally
(или except
) было бы уместнее.
(если все итерации цикла произошли без прерываний) — выполнился, иначе — нет.
Может, просто новое слово вести? Finally, except, на мой взгляд, уже имеют свои значения и использования. А вот, например, какое-нибудь "more" может подойдет? Типа, как ниже предложили: если цикл не прерван, то сделай еще что-нибудь. Если в других языках нет таких конструкций, то, может, новое слово необходимо?
условие
IF
что-делать ELSE
иное-делать THEN
Тут
THEN
переводится как «затем» (да, у него есть и такой перевод), но новичками на FORTH «сносит крышу» до такой степени, что многие делают себе синоним ENDIF
(в FORTH можно создавать самому управляющие конструкции).Только не стоит забывать, что в Python принят такой вариант else, который срабатывает, если цикл даже не выполнялся ни разу. )
for i in [1,2,3]:
print(i)
break
else:
print("else")
[Running] python -u "test.py"
1
[Done] exited with code=0 in 0.031 seconds
for i in []:
print("for")
break
else:
print("else")
[Running] python -u "tmp.py"
else
[Done] exited with code=0 in 0.037 seconds
Даже если бы он был интуитивно понятен, он едва ли был бы популярен.
Во-первых, программы часто пишут так, чтобы легко было переписать на другом языке. За исключением использования уникальных фишек языков в духе симтем типов, модулей и т.д., которые влияют скорее на архитектуру программы, а не на код функций. Тут же не только переносимость страдает, но и читаемость для программистов, не привыкших к такому, т.е. хоронится одно из преимуществ Питона.
Во-вторых, break и return в середине цикла — разновидности goto, как ни крути. Если они не сокращают и не упрощают код кардинально, ими обычно не пользуются. Зато если упрощают — пользуются во всю. Но тут, про упрощение, см. предыдущий пункт.
Во-первых, программы часто пишут так, чтобы легко было переписать на другом языке.
Рыыыли? То есть, в C++ не пишут на шаблонах?
А шаблоны к системе типов совсем не относятся?
Так я их именно потому и упомянул отдельно, что в отношении них остальное мною сказанное несправедливо.
А ещё указатели, препроцессор. Для Питона, наверное, будут list/dict comprehension, неявный возврат None из метода, возврат tuple из метода, менеджеры контекста, динамическая модификация модулей, классов(!) и объектов прямо в ходе работы.
Довольно много набегает вещей, которые мешают "на раз" перенести код в другой язык. Писать же в строго "универсальном" стиле, а-ля псевдокод — это терять все преимущества используемого языка, да и производительность тоже. Не получая вообще никаких плюшек.
Так что Ваш первый тезис, увы, не работает, совсем.
Про list comprehension и прочие подобные фишки согласен, но у меня акцент немного другой. То, как устроены основные управляющие конструкции языка — это более низкий уровень, нечто совсем буквальное. Если Вы пишете программы скажем на Си и на Форте (я конечно утрирую, и всё же), то понятно, что тела функций у них будут выглядеть очень по-разному. Но от программ на Си и Питоне уже не ждёшь такой разницы. При написании именно императивного кода на Питоне часто раздражает, например, отсутствие структурных областей видимости, хотя ими тоже можно и нужно при возможности пользоваться. И вот наличие else в for, по моему личному мнению, впечатление оставляет примерно такое же.
Но от программ на Си и Питоне уже не ждёшь такой разницы.Почему нет, чёрт побери? Программы на C работают примерно на два порядка быстрее, чем программы на Python — если я при этом ещё и вынуждеть писать программы на Python, как программы на C… то… «нафига козе баян»?
И вот наличие else в for, по моему личному мнению, впечатление оставляет примерно такое же.Мне это больше напоминает что-то типа возможности написать
x, y = y, x
. Простая и понятная фича, отсутствующая в C только потому что никто не посчитал её нужной туда добавить.Отказывать от неё, если от неё может быть пользя, для того, чтобы кто-то когда-то, может быть, переписал бы программу на C — просто глупо.
Простая и понятная фича, отсутствующая в C только потому что никто не посчитал её нужной туда добавить.
Простая и понятная. Ага. Не проще ли swap(&a, &b)?
Потому это ваша "простая и понятная" либо реально усложняет парсер, либо в каких-то граничных случаях может приводить к труднодиагностируемым багам
Не проще ли swap(&a, &b)?Нет, не проще.
swap
— это костыль, предназначенный для решения одной конкретной проблемы. Уже то, что это — не часть языка, а часть библиотеки — говорит о многом. Конструкции языка гибче.Если же вы считаете, что
swap
— это просто круть… ну перепишите вот такую программу с его использованием:a, b = 0, 1
for _ in range(40):
print(b)
a, b = b, a + b
Классика жанра, первые 40 чисел Фибоначчи.Потому это ваша «простая и понятная» либо реально усложняет парсерЗнаете — это, извините, даже не смешно. Усложняет… по сравнению с чем? С парсером языка, где ответ на задачу — «является ли вот вот эта вот последовательность байт программу на данном языке» алгоритмически неразрешим? Уж чья бы корова мычала…
либо в каких-то граничных случаях может приводить к труднодиагностируемым багамНе может. Нет там никаких «граничных случаев». В Python, как и в Pascal, приваивание — это не оператор, но выражение. Что резко упрощает парсер и устраняет сразу все граничные случаи.
И тот факт, что вы даже об это не знаете — показывает насколько часто эта «фича» нужна в C++…
Извините, но Вы мысли формулируете вообще никак (я тоже, кстати, виноват).
Не может. Нет там никаких «граничных случаев». В Python, как и в Pascal, приваивание — это не оператор, но выражение. Что резко упрощает парсер и устраняет сразу все граничные случаи.
т.е. речь шла про Python? Из ВАШЕГО исходного сообщения это не следует.
Мне это больше напоминает что-то типа возможности написать x, y = y, x. Простая и понятная фича, отсутствующая в C только потому что никто не посчитал её нужной туда добавить.
Отлично, скачем туда-сюда, потом обвиняем оппонента в том, что он НХНЗ. Я конкретно сказал, что в Си внедрение такой штуки… было бы опасным, Вы же не показали ни одного довода против.
Я конкретно сказал, что в Си внедрение такой штуки… было бы опасным, Вы же не показали ни одного довода против.Довод против очень прост, на самом деле. В C++ (а вы ведь
swap
из C++ притащили, не из C) такая фишка всё равно уже есть. То есть речь идёт только о том, что могут быть проблемы с парсером, если перетащить её из C++ в C.Но тут, как бы, по сравнению с тем, что там уже всё равно и так есть какой-нибудь
explicit [a, b] = [b, a];
ничего бы принципиально не изменил…В C++ (а вы ведь swap из C++ притащили, не из C) такая фишка всё равно уже есть.
ну, я имел в виду, что можно написать swap (я не сигнатуру функции написал, а именно ее вызов как в коде, памятуя, что сигнатура будет мерзкая типа swap(int*, int*)
— вот дерьмо). Хотя насколько я помню, в бытность Сей мы такое ради производительности на макросах фигачили.
А насчет проблем с парсером… Ну, Вы же помните чудесную историю с угловыми скобками в шаблонах в С++ (ну, типа надо было писать "< <" "> >" во вложенных шаблонах вместо "<<" ">>", иначе оно парсилось как оператор сдвига) — мне кажется, что все-таки ядро языка должно иметь однозначный набор фич, а парсер быть простым.
p.s. да, и прошу прощения за резкость — немного перегрелся, уже остыл, все ок.
мне кажется, что все-таки ядро языка должно иметь однозначный набор фич, а парсер быть простым.Если мы говорим о языке где построение AST требует наличия у парсера полноценного интерпретатора существенной части C++ и даже при этом для любого парсера есть последовательности байт, которые являются корректными программами на C++, но которые, при этом, оным парсером не принимаются… то тут уже как в пословице «снявши голову, по волосам не плачут».
Если же вы считаете, что swap
— это просто круть… ну перепишите вот такую программу с его использованием:
Извините, а зачем там вообще меняемое состояние?
std::iter::successors(Some((0, 1)), |&(a, b)| Some((b, a + b)))
.for_each(|(_, b)| println!("{}", x));
В Python, как и в Pascal, приваивание — это не оператор, но выражение. Что резко упрощает парсер и устраняет сразу все граничные случаи.
Вообще-то всё строго наоборот: не выражение, но оператор.
Мне это больше напоминает что-то типа возможности написать x, y = y, x
. Простая и понятная фича, отсутствующая в C только потому что никто не посчитал её нужной туда добавить.
Не поэтому, а потому, что в C уже есть оператор запятая.
Для наглядности стоит показать как будет обрабатываться без break, с срабатывающим break и когда условие цикла не выполнилось вообще:
i = 1
while i < 3:
print(i)
i += 1
else:
print("Конец")
12
Конец
i = 1
while i < 3:
print(i)
i += 1
if i == 2:
break
else:
print("Конец")
1i = 1000
while i < 3:
print(i)
i += 1
if i == 2:
break
else:
print("Конец")
Конец> о том как работает, но не почему
Это работает потому, что это Питон. В нем так устроено. Не имеет значения итератор там, или не итератор; важно, что это — цикл, у которого задан else. Остальное детали второстепенные.
А исключения тут при чем вообще?
break это не исключение. Исключения — это raise :-) Или Вы что-то другое имели в виду?
try:
while True:
f = files.next()
if f.uuid == match_uuid:
break
except StopIteration:
pass
else:
raise FileNotFound()
И else
от for
естественным образом превращается в else
от try
.лучшее объяснение из опубликованных в данном треде
Вы наверное забыли как устроены итераторы в Python.
никогда и не претендовал на это
next
. А выход из цикла for
происодит, когда этот метод бросает исключение. И вот else
— срабатывает как раз в его обработчике (а не в ветке else
от try
как мне тут подсказали — туда мы попадаем, как раз, из всех break
ов и не попадаем после выброса StopIteration
… netch80 порадовался бы: это ответ на его запрос).Только raise будет не в else, а в except StopIteration.
\@
я имел бы в желаемом виду, что вы читаете не только мой коммент, но и тот, на который я, отвечая, его писал. А там, внезапно, уот такоэ:
Но когда этого следующего элемента не будет, то есть будет конец, то будет возбуждаться исключение StopIteration. И вот, блок else будет выполняться, когда внутри цикла возбудиться это самое исключение.
А я собственно, и пишу туда, значицца — «а исключения тут при чем вообще»
Фейспалм, чес. слово.
switch
. А делать что-то удобным для человека, не знающего python, но знающего, внезапно, C — ну как минимум странная затея.А оператор множественного выбора кстати планируют ввести.
Не «С», а программирование в целом)То есть теперь дизайн языка должен опираться не на внутреннюю согласованность, а на «программирование в целом»? Интересная идея.
Я бы ещё понял если бы вы предложли в Python добавить
switch
и, как следствие, default
. Но добавить один-единственный default
потому что «выходцам из других ящыков будет комфортнее»… это уже ни в какие ворота…Сама эта статья является доказательством.Сама эта статья доказывает, что есть люди, которые хотят писать на Python не зная его — больше ничего.
С тем же самым подходом можно до хрипоты спорить про то — хорошая была идея использовать в C строки без указания длины или нет. Мне лично кажется, что это — одно из худших решений, когда-либо принятых в дизайне языков… но нам теперь с этим жить, извините.
То же самое касается и Python: нет нам ни
switch
, ни default
. Ибо они не нужны.Даже PEP 622 их не вводит.А делать что-то так, чтобы конструкция была бы понятна тем, кто знает некое абстрактное «программирование в целом» и была бы непонятна тем, кто хорошо знает ровно этот самый язык… не хочу ругаться матом.
А оператор множественного выбора кстати планируют ввести.Вот только ни слов
switch
, ни слов default
от этого в Python не появится.Да, конечно, можно сделать все языки программирования совершенно непохожими друг на друга даже в фундаментальных вещах. И наверное такие языки есть. Но зачем? Безусловно, бывают случаи, когда в предыдущих языках допускается некая ошибка дизайна, и ее исправляют. Такие ошибки есть в Си например, и их постепенно исправляют в языках нового поколения. А здесь что?
Но почему именно «else»?Там уже даже ссылку на YouTube привели: потому что это было предложение Кнута и потому что в те времена никто не ожидал, что когда-нибудь появлятся люди, не знающие о том, что
for
— это if
+ goto
.Эх, и почему мне кажется, что это всё так костыльно выглядит? Нет бы
let file = files.iter()
.find(|f| f.uuid == match_uuid)
.ok_or(FileNotFound)?;
let found = files.iter().any(|f| f.uuid.eq(match_uuid));
assert!(found);
и не надо ходить к шаману за толкованием. Справедливости ради, на питоне тоже (вроде) можно обойтись итератором, но вот эта возможность сделать через else только добавляет путаницы.
(let ([files '(aaa bbb ccc ddd)]
[match_uuid 'ccc])
(call/cc (lambda (found)
(map (lambda (uuid)
(if (eq? uuid match_uuid) (found uuid))) files)
(display 'not_found))))
Или на воображаемом питоне-с-продолжениями:
files = ['aaa', 'bbb', 'ccc', 'ddd']
match_uuid = 'ccc'
callcc(found):
for uuid in files:
if uuid == match_uuid:
found(uuid)
raise FileNotFound()
Тогда из самого языка можно было бы выкинуть исключения, циклы, генераторы и ещё кучу конструкций control flow, оставив только синтаксический сахар, а реализацию переложить в стандартную библиотеку.
Тогда из самого языка можно было бы выкинуть исключения, циклы, генераторы и ещё кучу конструкций control flow, оставив только синтаксический сахар, а реализацию переложить в стандартную библиотеку.После чего получили бы очередной язык для трёх с половиной гиков. Зачем? Их и так наизобретали тысячи.
Главный секрет блока else в циклах пайтона