Pull to refresh

Comments 161

Спасибо, отличное правило для запоминания.

Использование «else» не очень интуитивно. Не зная язык, логично предположить, что код ветки «else» вызывается, если не было ни одной итерации.
А он и вызовется, если не было ни одной итерации

Не было ни одной итерации — частный случай невыполнения оператора break. Если бы else срабатывал только в случае "не было ни одной итерации", тогда было бы более понятно. По моему мнению, конечно. А так для использования оператора else обязательно должен быть оператор break, что неочевидно. Хотя допускаю, что это оценка с т.зр. С-подобных языков; вполне возможно, что те, кто пишет на python, в курсе такого поведения и им нормально.

просто назвали его не совсем удачно. Если бы вместо else было finally, то его выполнение было бы интуитивнее
Я про это уже писал. В принципе вопрос «а не считаете ли вы, что 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.
Уважаемый khim,
вы берете на себя слишком большую ответственность додумывать за других.
Мой коммент был написан исключительно из моих соображений относительно перевода 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 — куда элегантнее…

Решение Python само по себе содержит скрытый флаг. Просто он не виден со стороны.
Мне оно нравится, но надо понимать, что оно весьма частично.

Ещё — пару раз встречалось, что в предполагаемом break слишком много общего кода и его таки надо вынести наружу (и тогда возникает тот же флаг, но в явном виде). Обдумывающему мне это, родилась мысль к else добавить then :)
Решение Python само по себе содержит скрытый флаг.
Это в каком месте, извините?

Если вы уберёте слово else и замените break на goto — то python-решение, внезапно, станет решением на C или C#… но флага в нём от этого не появится нигде.

Ещё — пару раз встречалось, что в предполагаемом break слишком много общего кода и его таки надо вынести наружу (и тогда возникает тот же флаг, но в явном виде).
Как и кому это поможет, извините? Либо вам нужен этот общий код всегда (в том числе и после прохождения по верке else) — тогда вам флаг и не нужен, либо не всегда — тогда этот код, извините, никак не «общий».
> Это в каком месте, извините?

Я считаю в варианте без goto. В варианте с goto таким флагом будет факт перехода. Но в Питоне нет goto, по крайней мере для программиста :)

> Как и кому это поможет, извините?

Если таких break несколько, и в них повторяется один и тот же код.
Если таких break несколько, и в них повторяется один и тот же код.
Ну вот совместными усилиями вам эту проблему решили… засовываете цикл в блок try, делаете raise вместо break
Угу, так будет работать. Только дорого (я как-то приучен — да, это не совсем по-питоновски — считать, что исключение это очень дорого и надо его избегать). Но если уже StopIteration это норма — что ж, тут так принято.
В си приходилось писать конструкции вида

Зачем? Просто создайте функцию:

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");
}


И можете использовать её на любых массивах.

Похоже вы только что переизобрели find из js :)


const good = array.find(isGood);
Не я и даже не авторы JS это придумали)
if (good == null) {
  printf("Got good item: ", good);
} else {
  printf("No good items\n");
}
Я жестко туплю или блоки условий действительно перепутаны местами?
Ведь если выполняется условие good == null то у нас нет нужных элементов и надо показывать второй блок.
Перепутаны, конечно. И это, собственно, показывает почему подход python гораздо понятнее — вам не нужно думать о том что у вас обозначает флаг, вам не нужно думать том, что элемент, который вы ищите может, внезапно, и сам иметь значение 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 В прод я бы такое не писал, не поймут-с.

И правильно, что не поймут. Оформляйте функцией с несколькими return, смысл тот же, а читается проще.
Мне для коротких участков — легче с goto, чем следить за тем, что происходит на границе вызова функции, пусть даже лямбды. В C++ там могут быть неожиданные интересные эффекты типа копирования, когда его не ждёшь.
В статье не хватает главного вывода: не надо так никогда писать и пропускать на ревью если кто-то так пишет. В любом языке есть полно плохо продуманных фич которыми лучше просто не пользоваться.
UFO just landed and posted this here
Я считаю, что это вредная фича, т.к. сбивает с толку людей, которые пользуются не только Python-ом но и другими языками.
> Если вы не хотите ее использовать, может быт вы выбрали не тот язык?
Может быть. А может просто maintainer'ы Python добавили в язык очередной кривой костыль. Впрочем ничего нового (с)

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

А почему бы тогда для этой фичи не придумать другое слово и не использовать else? :)

Мне ок (но кстати какое к примеру?). Но если вдруг будут добавлять в существующий язык то добавят else чтобы не добавлять новые ключевые слова (новые ключевые слова это проблема для обратной совместимости)

На мой взгляд было-бы логичнее поставить что-нить вроде finally. Слово не новое, известное, и логично передаёт, что мы завершаем цикл.
А 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 всегда срабатывает.
Получается если нет 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 и проверять после цикла перед выводом значения.

Теперь понятно, но в этом коде все ещё есть break.


"Получается если нет break, то else слово тоже бессмысленно, можно просто код следом воткнуть, разве нет?"

Не должен, но может быть.
Этот блок else выполнится и для цикла for, и для цикла while если сам цикл не был прерван.

В случае с while у вас вызовется либо первый блок, либо второй, но не оба сразу

Наоборот. Оба сразу и вызовутся. Не самая очевидная штука.

try… except… else
Нет проблем? Вроде как всё очевидно.

Не очевидно. В предыдущих двух кейсах else относился к логическому условию, а тут никакого явного условия нет. Учитывая, что там ещё finally встречается, я бы назвал третий блок как-нибудь в духе noexcept.


for x in y… else
А тут вдруг почему непонятно стало?

По той же причине — else должен относиться к логическому условию, которого тут нет.


Конечно, это можно запомнить. Но это именно неочевидный момент, который надо специально заучивать, очень не по-питоновски.

m.youtube.com/watch?feature=youtu.be&v=OSGv2VnC0go&t=950
  • 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, не хотите добавить этот комментарий в статью? Интересный, и понятно откуда взялся такой странный синтаксис.

> was devised by Donald Knuth

Приходил к аналогичным мыслям. :) По-моему, довольно очевидно, если есть условие, else напрашивается само собой.

На видео, кстати, в этом месте у них ляп явный, функция принимает аргумент target, потом (внезапно) внутри tgt.
Я как раз из тех, кто «пользуются не только Python-ом». Пропустил через себя статью и комментарии и, кажется, нашел к чему все-таки относится «else». Он как и положено связан с «if», который скрыт внутри условного выражения продолжения выполнения цикла :
Для какого-нибудь
while(a < 10):
это само условное выражение a<10,
а внутри
for f in files:
— это условие «есть ли еще файлы в списке files? — нет, тогда выполняем то, что в блоке else после цикла». Для try/catch except все не так логично, тем более, что это не цикл. Скорее здесь это условие «В блоке try: было исключение?» — нет, тогда переходим в блок внутри else. Замечу, что здесь может и не быть никаких блоков raise, или return, поэтому будет странно звучать, что else относится к тому чего нет.
UFO just landed and posted this here
+290.

«Эта конструкция», пишет автор ответа, «весьма странная даже для опытных кодеров на Питоне». И продолжает: «лучше либо поиск в функцию вынести, либо заюзать list comprehension»
Тут проблема курицы и яйца: поскольку её, по неизвестной науке причине, активно избегают — то она «странно выглядит». А раз она «странно выглядит», то её и избегают.

Есть куда более странные фичи в python, которых, почему-то, не избегают. Чего один тернатный оператор ha if foo() else hi стоит…
Когда-то, когда мне в голову пришла такая конструкция как «else после for», я подразумевал else как противовес оператора условия цикла.

Но вспоминая этот замысел сейчас, я задаюсь таким вопросом: «else должен сработать только однажды — если тело цикла ни разу не выполнялось?». По-моему, такой была моя первоначальная идея. А можно считать, что раз речь про цикл, то в любом случае, как только условие стало ложным, независимо от кол-ва итераций. Иными словами, и при окончании цикла — тоже. В Python используется этот второй вариант.

В общем, несмотря на то, что else иногда позволяет уменьшить copy-paste (например не дублировать условие), тут есть неочевидные моменты. Более того, может иметь право на существование и 3-й вариант (гипотетически): else выполняется именно тогда, когда условие было истинным, а потом стало ложным. То есть, только в том случае, если какое-то кол-во итераций уже было выполнено, а если их не было вовсе — нет.

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

Поскольку в этом случае начинает выполняться то, что за циклом, достаточно записать действия сразу за циклом, и else не нужен.

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

Его удобство видится именно тогда, когда вводишь дополнительную переменную «а не случился ли нужный нам break».
> Поскольку в этом случае начинает выполняться то, что за циклом, достаточно записать действия сразу за циклом, и else не нужен.

«Поскольку программа выполняется последовательно нужно просто записывать одну строку под другой» Гениально, браво!

А то, что «за циклом» можно оказаться как побывав в нем, так и не побывав — ни на какие мысли не наводит? А вот для логики программы, путь, который привел к этой самой точке «за циклом», может быть существенным, именно потому-то там else, а не просто «пустая строка, пишите код дальше!»

Вот тут-то, например, наличие того же break (из хотя бы раз начатого выполняться цикла), который досрочно прерывает цикл и сказывается — «break отменяет else».
> А то, что «за циклом» можно оказаться как побывав в нем, так и не побывав — ни на какие мысли не наводит?
> А вот для логики программы, путь, который привел к этой самой точке «за циклом», может быть существенным

Наверно, тут нужно собирать статистику вариантов важности ситуации, типа:

1) Нам не нужно вообще что-то делать, если цикл не выполнился ни разу или break не вызван, всё уже сделано. (Банально и даёт подавляющее большинство, не интересно.)
2) Нам нужно реагировать на то, что break не вызван, но не важно, была ли хоть одна итерация при этом или нет. (То, на что заточен else сейчас в Python.)
3) Нам нужно реагировать на то, что было 0 итераций, но не важно, вышли мы по break или нет.
4) Что-то ещё более сложное. (Бывает, и нередко.)

На любое введение хитрой конструкции в языке обычно требуются обоснования для реально большого количества вариантов применения. Я правильно вас понял, вы агитируете за (3)? И при этом нельзя заранее проверить, будет ли выполнение? Если да, вопрос — что у вас за специфика, что это настолько важно, что хочется отдельной конструкции? А то моя практика показывает, что (2) достаточно частый случай, а (3) — ну честно не помню.
> тут нужно собирать статистику вариантов важности ситуации
> …
> Я правильно вас понял, вы агитируете за (3)?

могу только процитировать свой ранее данный ответ:
Вариант, который принят в Python, наиболее, пожалуй, логичный, но самый ли он удобный, сказать не возьмусь — нужно было бы проанализировать огромную кучу кода, и выбрать, какая из версий этой идиомы оптимальней. Пока же, по косвенным признакам можно заключить, что востребованность у нее весьма низкая.
OK. В ответе по ссылке вы сформулировали так, что сложно было понять идею. Пока что я хотел начать с понимания, правильно ли, что (3) — то, что вы подразумевали. Для полноты истории всё-таки лучше ответить.
Что это за «косвенные признаки» у вас — я не знаю, но я неоднократно использовал такую конструкцию. По-нормальному таки надо было бы набрать статистику, но у меня на это не скоро будет время.
Блок 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.

> if отменяет else

Не согласен. Кроме того, не надо путать условный оператор, коим if-else и является и, образно говоря, goto из его веток.
— Как раз неудачная формулировка, которую не нужно запоминать, потому что «относится» никак никому не поможет. Более того — она ошибочна, потому что даже если 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 никак не подойдет finallyfinally исполняется всегда, было исключение или не было
#!/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 — на порядок понтнее и проще, чем то, что вы наворотили в C++.

Что очень хорошо видно потому, что в 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(' * ')}`
А ничего, что это — совсем другой алгоритм, не имеющий ровно ничего общего с тем, что обсуждал ваш собеседник? Впервые вижу человека, который, вроде как умеет в математику, но при этом — типичный ГСМ
Это какого хрена он другой? То, что я не описал очевидную функцию get_divisors, которая, по сути, просто повторяет внутренний цикл предыдущего оратора — ну так я ожидал, что здесь собрались достаточно умные люди, чтобы до этого додуматься.

Ну уж извините, что преувеличил ваши интеллектуальные способности.

Или вынести часть кода в функцию — это «совсем другой алгоритм»? Ну тогда почитайте, что такое алгоритм.

Вот, жду извинений


На питоне, увы, не пишу.
Код
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 можно было и не вводя лишнюю функцию, да ещё и такую странную?

Блин, где я хоть слово сказал об избавлении от for? Или о цикле while? Я написал как раз наоборот:
по сути, просто повторяет внутренний цикл предыдущего оратора

А чем функция странная?

А, я ветку перепутал. Да, тут замена цикла не обсуждалась.


Функция странная тем, что она возвращает bool, закодированный в длине массива. Я не вижу у нее других применений кроме как проверить длину массива.

У неё есть другие применения даже в моем примере — узнать делители числа

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

Но не все, а только минимальный и максимальный несобственные.
Так, как в оригинале. А чуть ниже пример, где все. Вы через предложение читаете?

При этом для простых чисел она почему-то возвращает максимальный собственный...
И в этом есть своя логика, подумайте
TheShock,
В том то и дело, что если делать «дедовским способом» как в Вашем примере, то это вызов функции, хранение списка результатов, дополнительное ветвление, а этa «странная конструкция» в питоне делает все без лишних манипуляций.

И глядя на этот контраст, здоровый минимализм который показывает в этом моменте питон — завораживает. Решение выдается в тот момент когда решение готово, и не шагом позже. На этом фоне все остальное смотрится как расточительство. Если приложить к этому философию бережливого производства (Lean), то это потери хранения, транспортировки, ожидания, излишней обработки.
А где, собственно, сам файл, который мы собрались обрабатывать? И вы не забыли, что files — это может быть и генератор?

Возможно что-то типа finally (или except) было бы уместнее.
(если все итерации цикла произошли без прерываний) — выполнился, иначе — нет.

Может, просто новое слово вести? Finally, except, на мой взгляд, уже имеют свои значения и использования. А вот, например, какое-нибудь "more" может подойдет? Типа, как ниже предложили: если цикл не прерван, то сделай еще что-нибудь. Если в других языках нет таких конструкций, то, может, новое слово необходимо?

Пришло на ум ещё одно правило для запоминания, основанное на другом переводе английского слова «else» — то бишь «ещё» (см. «what else?»). Т.е. примерно так: «если цикл отработал полностью и не был прерван, что ещё надо сделать?». И да, и это действительно входит в противоречие с более привычным значением «иначе».
Очень похожим образом читается условный оператор на FORTH:
    условие IF что-делать ELSE иное-делать THEN

Тут THEN переводится как «затем» (да, у него есть и такой перевод), но новичками на FORTH «сносит крышу» до такой степени, что многие делают себе синоним ENDIF (в FORTH можно создавать самому управляющие конструкции).

Это тоже входит в "отработал полностью".

не выполнившийся ни разу блок кода «полностью отработал».

Штош

А вы не путайте цикл и тело цикла. Цикл выполнился 1 раз и полностью отработал Тело цикла при этом может не быть выполнено ни разу, и тут нет никакого противоречия.

Ну вы б хоть прогнали то, что сами написали, тогда бы поняли, что 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, то в конце тоже будет else. Ниже я постараюсь дать комментарий

Даже если бы он был интуитивно понятен, он едва ли был бы популярен.


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


Во-вторых, 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("Конец")
1
2
Конец
i = 1
while i < 3:
     print(i)
     i += 1
     if i == 2:
        break
else:
    print("Конец")
1
i = 1000
while i < 3:
     print(i)
     i += 1
     if i == 2:
        break
else:
    print("Конец")
Конец

Спасибо, теперь более понятно что лучше не использовать в проде

На самом деле здесь было дано много комментариев, о том как работает, но не почему. На мой взгляд объяснить почему это так работает можно следующим образом: по мере прохождения по итерируемому объекту, у нас будет браться каждый раз следующий элемент. Но когда этого следующего элемента не будет, то есть будет конец, то будет возбуждаться исключение StopIteration. И вот, блок else будет выполняться, когда внутри цикла возбудиться это самое исключение.
А исключения тут при чем вообще?

> о том как работает, но не почему

Это работает потому, что это Питон. В нем так устроено. Не имеет значения итератор там, или не итератор; важно, что это — цикл, у которого задан else. Остальное детали второстепенные.
А исключения тут при чем вообще?

break это не исключение. Исключения — это raise :-) Или Вы что-то другое имели в виду?

Вы наверное забыли как устроены итераторы в Python. Оригинальный цикл из статьи раскрывается примерно вот в это:
try:
  while True:
    f = files.next()
    if f.uuid == match_uuid:
        break
except StopIteration:
  pass
else:
  raise FileNotFound()
И else от for естественным образом превращается в else от try.

лучшее объяснение из опубликованных в данном треде


Вы наверное забыли как устроены итераторы в Python.

никогда и не претендовал на это

Ну вот теперь будете знать: у итераторов в Python ровно один методnext. А выход из цикла for происодит, когда этот метод бросает исключение. И вот else — срабатывает как раз в его обработчике (а не в ветке else от try как мне тут подсказали — туда мы попадаем, как раз, из всех breakов и не попадаем после выброса StopIterationnetch80 порадовался бы: это ответ на его запрос).

Заклёпкометрии ради:


__iter__

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

__iter__ возвращает итератор, а не является методом итератора.

Вы путаете «итерируемый тип» и «тип итератора». Это разные сущности (хотя часто реализуются в одном классе, да).

Только raise будет не в else, а в except StopIteration.

> break это не исключение. Исключения — это raise :-) Или Вы что-то другое имели в виду?

\@

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

А я собственно, и пишу туда, значицца — «а исключения тут при чем вообще»

Фейспалм, чес. слово.
Забавно что я тоже думал какой комментарий написать важнее первым (у меня 1 пост в час :)) — как у вас с идеей о StopIteration-исключении, или вот этот про «историю вопроса» от одного из разработчиков питона Реймонда Хеттинджера, и в итоге остановился на втором случае, потому что посчитал что первый потребует от меня больше одного поста :)

У вас тоже хороший ответ) освящает историную сторону вопроса, что тоже очень интересно. А идея с nobreak — шикарная- я б заменил в питоне вместо else :)

Фича интересная, но вот ключевое слово «else» выбрано ИМХО неудачно. Возможно, по смыслу лучше подошло бы слово «default» — это общепринятое слово для оператора switch, где также применяется break.
Вот только в python нет оператора switch. А делать что-то удобным для человека, не знающего python, но знающего, внезапно, C — ну как минимум странная затея.
Не «С», а программирование в целом) Все эти ключевые слова — уже стандарт де-факто, и вряд ли имеет смысл ломать эту логику в каком-то одном языке. Сама эта статья является доказательством. Синтаксис и семантика операторов структурного программирования уже так же привычны, как арифметические и логические операции и их приоритет.
А оператор множественного выбора кстати планируют ввести.
Не «С», а программирование в целом)
То есть теперь дизайн языка должен опираться не на внутреннюю согласованность, а на «программирование в целом»? Интересная идея.

Я бы ещё понял если бы вы предложли в Python добавить switch и, как следствие, default. Но добавить один-единственный default потому что «выходцам из других ящыков будет комфортнее»… это уже ни в какие ворота…

Сама эта статья является доказательством.
Сама эта статья доказывает, что есть люди, которые хотят писать на Python не зная его — больше ничего.

С тем же самым подходом можно до хрипоты спорить про то — хорошая была идея использовать в C строки без указания длины или нет. Мне лично кажется, что это — одно из худших решений, когда-либо принятых в дизайне языков… но нам теперь с этим жить, извините.

То же самое касается и Python: нет нам ни switch, ни default. Ибо они не нужны.Даже PEP 622 их не вводит.

А делать что-то так, чтобы конструкция была бы понятна тем, кто знает некое абстрактное «программирование в целом» и была бы непонятна тем, кто хорошо знает ровно этот самый язык… не хочу ругаться матом.

А оператор множественного выбора кстати планируют ввести.
Вот только ни слов switch, ни слов default от этого в Python не появится.
Речь идет об изначальном дизайне языка, а не о том чтобы что-то менять в существующем. Не обязательно было использовать именно «default», есть и другие слова. Но почему именно «else»? Это действительно нетипичное использование данного ключевого слова.
Да, конечно, можно сделать все языки программирования совершенно непохожими друг на друга даже в фундаментальных вещах. И наверное такие языки есть. Но зачем? Безусловно, бывают случаи, когда в предыдущих языках допускается некая ошибка дизайна, и ее исправляют. Такие ошибки есть в Си например, и их постепенно исправляют в языках нового поколения. А здесь что?
Но почему именно «else»?
Там уже даже ссылку на YouTube привели: потому что это было предложение Кнута и потому что в те времена никто не ожидал, что когда-нибудь появлятся люди, не знающие о том, что for — это if + goto.
Как раз про это я в курсе, мне сама идея вполне понравилась.

Эх, и почему мне кажется, что это всё так костыльно выглядит? Нет бы


let file = files.iter()
    .find(|f| f.uuid == match_uuid)
    .ok_or(FileNotFound)?;
Уж лучше так
val file = files.firstOrNull { f -> f.uuid == match_uuid } ?: throw FileNotFound()
Как бы это выглядело в Rust:
let found = files.iter().any(|f| f.uuid.eq(match_uuid));
assert!(found);

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

Конкретно с этим примером проблема в том, что мы не получаем, собственно, нужный нам файл.

Как и в оригинальном коде статьи.
А могли бы реализовать
продолжения (continuations)
(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, оставив только синтаксический сахар, а реализацию переложить в стандартную библиотеку.
После чего получили бы очередной язык для трёх с половиной гиков. Зачем? Их и так наизобретали тысячи.
Sign up to leave a comment.

Articles