Как я могу понять условие `else` циклов Python?


191

Многие программисты Python, вероятно, не знают, что синтаксис whileциклов и forциклов включает необязательное else:предложение:

for val in iterable:
    do_something(val)
else:
    clean_up()

Тело elseпредложения является хорошим местом для определенных видов действий по очистке и выполняется при обычном завершении цикла: то есть, выход из цикла с предложением returnили breakпропуск elseпредложения; выход после того, как continueвыполняет его. Я знаю это только потому, что только что посмотрел (еще раз), потому что не могу вспомнить, когда было выполнено elseпредложение.

Всегда? На «провал» цикла, как следует из названия? На регулярное прекращение? Даже если цикл завершен с return? Я никогда не могу быть полностью уверен, не глядя на это.

Я виню свою сохраняющуюся неопределенность в выборе ключевого слова: я нахожу elseневероятно немонемным для этой семантики. Мой вопрос не в том, «почему это ключевое слово используется для этой цели» (за который я бы, вероятно, проголосовал бы, хотя бы только после прочтения ответов и комментариев), а как я могу подумать о elseключевом слове, чтобы его семантика имела смысл, и я может поэтому помнить это?

Я уверен, что было довольно много обсуждений по этому поводу, и я могу себе представить, что выбор был сделан для соответствия с предложением tryзаявления else:(которое я также должен посмотреть), и с целью не добавлять в список Зарезервированные слова Python. Возможно, причины выбора elseпрояснят его функцию и сделают его более запоминающимся, но я после соединения имени с функцией, а не после исторического объяснения как такового.

Ответы на этот вопрос , который мой вопрос был кратко закрыт как дубликат, содержат много интересных предысторий. Мой вопрос имеет другую направленность (как связать определенную семантику elseс выбором ключевого слова), но я чувствую, что где-то должна быть ссылка на этот вопрос.


23
Как насчет «если есть что-то еще, чтобы повторить ... еще»
OneCricketeer

4
Я думаю, что вы можете помнить это сейчас после написания этого вопроса :)
Джаспер

11
в elseосновном означает «если условие продолжения не выполнено». В традиционном цикле for условием продолжения обычно является i < 42: в этом случае вы можете просмотреть эту часть какif i < 42; execute the loop body; else; do that other thing
njzk2

1
Это все верно, и мне особенно нравится ответ от drawoc, но нужно учитывать и то, что else - это доступное ключевое слово, которое также имеет синтаксический смысл. Возможно, вы знаете, попробовать / исключить и, возможно, попробовать / исключить / окончательно, но также есть еще - запустить этот код, если не произошло исключений. Что, кстати, не то же самое, что вставлять этот код в предложение try - обработка исключений лучше всего использовать, когда нацелена узко. Так что, хотя это имеет концептуальный смысл - согласно ряду ответов здесь - я думаю, что это также многократное повторное использование ключевых слов - запустите это при определенных условиях .
JL Peyret

1
@Falanwe, есть разница, когда код выходит из break. Канонический вариант использования - это когда цикл ищет что-то и прерывается, когда он это находит. elseВыполняется только тогда , когда ничего не найдено.
Алексис

Ответы:


213

(Это вдохновлено ответом @Mark Tolonen.)

ifОператор запускает его elseпредложение , если его состояние оценивается как ложное. Идентично, whileцикл запускает предложение else, если его условие оценивается как ложное.

Это правило соответствует описанному вами поведению:

  • При нормальном выполнении цикл while выполняется повторно до тех пор, пока условие не станет ложным, и, следовательно, при естественном выходе из цикла выполняется условие else.
  • Когда вы выполняете breakоператор, вы выходите из цикла без оценки условия, поэтому условие не может быть оценено как ложное, и вы никогда не выполняете предложение else.
  • Когда вы выполняете continueоператор, вы снова оцениваете условие и делаете то, что обычно делаете в начале итерации цикла. Итак, если условие истинно, вы продолжаете цикл, но если оно ложно, вы запускаете предложение else.
  • Другие методы выхода из цикла, такие как return, не оценивают условие и поэтому не запускают предложение else.

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


8
Это самый отличный ответ. Рассматривайте свои циклы как серию операторов elif, и поведение else будет раскрывать его естественную логику.
Номенатор

1
Мне также нравится этот ответ, но он не проводит аналогии с рядом elifутверждений. Есть ответ, который делает, и у него есть одно чистое голосование.
Алексис

2
ну, не совсем, цикл while может иметь условие, соответствующее False, непосредственно перед тем, как оно будет выполнено break, и в этом случае elseусловие не будет выполняться, но условие будет False Точно так же с forциклами это может breakна последнем элементе.
Tadhg McDonald-Jensen

36

Лучше думать об этом так: elseблок будет всегда выполняться , если все идет прямо в предыдущем forблоке таким образом, что она достигает истощения.

Право в этом контексте будет означать нет exception, нет break, нет return. Любое утверждение, от которого перехватывает управление for, приведет к elseобходу блока.


Обычный вариант использования обнаруживается при поиске элемента в iterable, для которого поиск либо отменяется, когда элемент найден, либо "not found"флаг поднимается / печатается с помощью следующего elseблока:

for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")  

A continueне перехватывает управление у for, поэтому управление переходит к тому, elseкогда forистощен.


21
Звучит очень хорошо ... но тогда вы ожидаете, что elseпредложение будет выполнено, когда дела пойдут не так , не так ли? Я уже запутался снова ...
Алексис

Я должен не согласиться с вами по поводу «Технически, он [семантически не похож на все остальные else]», поскольку elseон запускается, когда ни одно из условий в цикле for не принимает значение True, как я демонстрирую в своем ответе
Tadhg McDonald- Дженсен

@ TadhgMcDonald-Jensen Вы также можете разорвать цикл на False. Таким образом, вопрос о том, как forэто сломано, зависит от варианта использования.
Моисей Коледое

Правильно, я прошу способ каким-то образом связать то, что происходит, с английским значением "else" (что действительно отражается в других случаях использования elseв python). Вы предоставляете хорошую интуитивную сводку о том else, что делает @Moses, но не о том, как мы могли бы связать это поведение с «else». Если бы использовалось другое ключевое слово (например, nobreakкак упомянуто в этом ответе на связанный вопрос), это было бы легче понять.
Алексис

1
Это действительно не имеет ничего общего с "все идет хорошо". Остальное выполняется чисто, когда условие if/ whileоценивается как ложное или forнет элементов. breakсуществует содержащий цикл (после else). continueвозвращается и снова оценивает состояние цикла.
Марк Толонен

31

Когда ifвыполнить else? Когда его условие ложно. Это точно так же для while/ else. Таким образом, вы можете думать о while/ elseкак о просто, ifкоторый продолжает работать в своем истинном состоянии, пока не оценит ложное. А breakэто не меняет. Он просто выпрыгивает из содержащего цикла без оценки. elseВыполняется только при оценке в if/ whileусловие ложно.

forАналогично, за исключением его ложное условие исчерпав итератор.

continueи breakне казнить else. Это не их функция. Выход breakиз содержащего цикла. continueВосходит к верхней части , содержащей петли, где вычисляется условие цикла. Это акт оценки if/ whileложь (или forне имеет больше элементов), который выполняется, elseи никак иначе.


1
То, что вы говорите, звучит очень разумно, но объединение трех условий завершения «до тех пор, пока [условие] не станет ложным или не прервется / продолжится», неверно: важно, что elseпредложение выполняется, если цикл завершается с continue(или обычно), но нет, если мы выйдем с break. Именно из-за этих тонкостей я пытаюсь понять, что elseловит, а что нет.
Алексис

4
@alexis да мне нужно уточнить там. Ред. continue не выполняет else, но возвращает к началу цикла, который может затем получить значение false.
Марк Толонен

24

Вот что это по сути означает:

for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

Это более хороший способ написания этого общего шаблона:

found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

Предложение elseне будет выполнено, если есть, returnпотому что returnоставляет функцию, как это и предполагалось. Единственное исключение из того, о чем вы, возможно, думаете, это то finally, чья цель - быть уверенным, что оно всегда выполняется.

continueне имеет ничего общего с этим вопросом. Это приводит к тому, что текущая итерация цикла заканчивается, что может привести к завершению всего цикла, и, очевидно, в этом случае цикл не был завершен с помощью a break.

try/else похож:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...

20

Если вы думаете о своих циклах как о структуре, похожей на эту (несколько псевдокод):

loop:
if condition then

   ... //execute body
   goto loop
else
   ...

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

Так что обратите внимание, что else единственное получение выполняется при проверке условия . Это означает, что если вы выйдете из тела цикла в середине выполнения, например, с помощью a returnили a break, поскольку условие не проверяется снова, elseслучай не будет выполнен.

A, continueс другой стороны, останавливает текущее выполнение, а затем переходит назад, чтобы снова проверить состояние цикла, поэтому elseв этом сценарии можно достичь этого.


Мне очень нравится этот ответ, но вы можете упростить: опустите endярлык и просто поместите его goto loopвнутрь ifкорпуса. Может быть, даже превзойти, поместив надпись ifна той же строке, что и метка, и это внезапно очень похоже на оригинал.
Берги

@ Берджи Да, я думаю, это немного понятнее, спасибо.
Кейван

15

Мой момент с elseпунктом цикла был, когда я смотрел лекцию Рэймонда Хеттингера , который рассказал историю о том, как, по его мнению, это должно было быть названо nobreak. Посмотрите на следующий код, как вы думаете, что он будет делать?

for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

Что бы вы предположили, что это делает? Ну, часть, которая говорит, nobreakбудет выполнена, только если breakоператор не попадет в цикл.


8

Обычно я склонен думать о такой структуре цикла:

for item in my_sequence:
    if logic(item):
        do_something(item)
        break

Чтобы быть очень похожим на переменное количество if/elifутверждений:

if logic(my_seq[0]):
    do_something(my_seq[0])
elif logic(my_seq[1]):
    do_something(my_seq[1])
elif logic(my_seq[2]):
    do_something(my_seq[2])
....
elif logic(my_seq[-1]):
    do_something(my_seq[-1])

В этом случае elseоператор в цикле for работает точно так же, как elseоператор в цепочке elifs, он выполняется только в том случае, если ни одно из условий перед тем, как он оценивается как True. (или прервать выполнение с помощью returnили исключение) Если мой цикл не соответствует этой спецификации, обычно я выбираю отказаться от использования for: elseпо той причине, по которой вы задали этот вопрос: он не интуитивен.


Правильно. Но цикл запускается несколько раз, поэтому немного неясно, как вы хотите применить это к циклу for. Вы можете уточнить?
Алексис

@alexis Я переделал свой ответ, думаю, теперь он стал намного понятнее.
Тадхг Макдональд-Дженсен

7

Другие уже объяснили механику while/for...else, а также справочник по языку Python 3 имеет официальное определение (см в то время как и для ), но вот мой личный мнемонические, FWIW. Я думаю, что ключом для меня было разделить это на две части: одну для понимания смысла elseотносительно условного цикла, а другую для понимания управления циклом.

Я считаю, что проще всего начать с понимания while...else:

whileу вас есть больше предметов, делать вещи, elseесли у вас кончились, сделайте это

for...elseМнемонические в основном то же самое:

forкаждый предмет, делать вещи, но elseесли у вас кончились, сделайте это

В обоих случаях эта elseчасть достигается только в том случае, если больше нет элементов для обработки, а последний элемент обработан обычным образом (т. Е. Нет breakили return). А continueпросто возвращается и видит, есть ли еще предметы. Моя Мнемоника для этих правил относится как whileи for:

когда ты делаешь то breakили returnиное elseдело, ничего не поделаешь,
а когда я говорю continue, для тебя это «возврат к началу»

- с «loop back to start», означающим, очевидно, начало цикла, где мы проверяем, есть ли еще какие-либо элементы в итерируемом, поскольку elseэто continueдействительно не играет никакой роли.


4
Я полагаю, что это можно улучшить, сказав, что обычная цель цикла for / else состоит в том, чтобы проверять элементы до тех пор, пока вы не найдете то, что ищете, и не захотите остановиться , или у вас кончатся элементы. «Else» существует для обработки части «у вас закончились предметы (не найдя того, что вы искали)».
суперкат

@supercat: Может быть, но я не знаю, каковы наиболее распространенные варианты использования. elseМожет также использоваться , чтобы сделать что - то , когда вы просто закончили со всеми пунктами. Примеры включают написание записи в журнале, обновление пользовательского интерфейса или сигнал о каком-то другом процессе, который вы сделали. Ничего действительно. Кроме того, некоторые фрагменты кода имеют «успешный» конец дела с breakвнутренним циклом, и elseон используется для обработки «ошибки», когда вы не нашли подходящего элемента во время итерации (возможно, именно об этом вы и думали из?).
Фабиан Фагерхольм

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

Если вам не нужно проводить различие между случаем, когда цикл прошел все итерируемые элементы без прерывания (и это был успешный случай), и случаем, когда он не прошел. Затем вы должны поместить «завершающий» код в elseблок цикла или отслеживать результат, используя другие средства. Я в основном согласен, я просто говорю, что не знаю, как люди используют эту функцию, и поэтому я бы хотел избежать предположений о том, является ли elseсценарий « обрабатывает успешное дело» или « elseобрабатывает неудачное дело» более распространенным. Но у вас есть хорошая точка зрения, поэтому комментируйте, проголосовали!
Фабиан Фагерхольм

7

В управляемой тестами разработке (TDD) при использовании парадигмы Transformation Priority Premise вы рассматриваете циклы как обобщение условных операторов.

Этот подход хорошо сочетается с этим синтаксисом, если вы рассматриваете только простые if/else(нет elif) операторы:

if cond:
    # 1
else:
    # 2

обобщает до:

while cond:  # <-- generalization
    # 1
else:
    # 2

приятно.

На других языках TDD переходит от одного случая к случаям с коллекциями и требует большего рефакторинга.


Вот пример из блога 8thlight :

В связанной статье в блоге 8thlight рассматривается ката Word Wrap: добавление разрывов строк к строкам ( sпеременная в фрагментах ниже), чтобы они соответствовали заданной ширине ( lengthпеременная в фрагментах ниже). В какой-то момент реализация выглядит следующим образом (Java):

String result = "";
if (s.length() > length) {
    result = s.substring(0, length) + "\n" + s.substring(length);
} else {
    result = s;
}
return result;

и следующий тест, который в настоящее время не проходит:

@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
    }

Итак, у нас есть код, который работает условно: при выполнении определенного условия добавляется разрыв строки. Мы хотим улучшить код для обработки нескольких разрывов строк. Представленное в статье решение предлагает применить преобразование (if-> while) , однако автор комментирует следующее:

В то время как у циклов не может быть elseпредложений, поэтому мы должны устранить elseпуть, делая меньше в ifпути. Опять же, это рефакторинг.

что заставляет делать больше изменений в коде в контексте одного неудачного теста:

String result = "";
while (s.length() > length) {
    result += s.substring(0, length) + "\n";
    s = s.substring(length);
}
result += s;

В TDD мы хотим написать как можно меньше кода для прохождения тестов. Благодаря синтаксису Python возможно следующее преобразование:

из:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

чтобы:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s

6

Как я вижу, else:срабатывает, когда вы перебираете конец цикла.

Если вы breakили returnили raiseвы не итерация в конце прошлого цикла, вы перестаете immeadiately, и , таким образом, else:блок не будет работать. Если вы continueвсе еще итерируете после окончания цикла, поскольку продолжить просто пропускает к следующей итерации. Это не останавливает цикл.


1
Мне нравится это, я думаю, что вы к чему-то. Это немного связано с тем, как в прежние времена были реализованы циклы до появления ключевых слов цикла. (А именно: чек был размещен в нижней части цикла, причем gotoвершина была успешной.) Но это более короткая версия ответа с
наибольшим количеством

@alexis, субъективно, но мне легче выразить свой способ выражения.
Уинстон Эверт

на самом деле я согласен. Хотя бы потому, что это более емко.
Алексис

4

Думайте о elseпредложении как о части конструкции цикла; breakполностью выходит из конструкции цикла и пропускает elseпредложение.

Но на самом деле мое умственное отображение состоит в том, что это «структурированная» версия шаблона C / C ++:

  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

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

(Я поместил «структурированный» в страшные кавычки, потому что разница не в том, является ли код структурированным или неструктурированным, а просто в том, есть ли ключевые слова и грамматика, выделенные для конкретной структуры)


1
Где находится else? Если вы имели в виду, что done:лейбл стоит в качестве прокси-сервера или else:, я полагаю, у вас это точно в обратном направлении.
Алексис

@alexis «еще» код заполнит в «...» непосредственно перед на done:этикетке. Общее соответствие, пожалуй, лучше всего сказать так: Python имеет конструкцию else-on-loop, так что вы можете выразить этот шаблон потока управления без goto.
Звол

Существуют и другие способы выполнения этого шаблона потока управления, например, путем установки флага. Вот чтоelse избегает.
Алексис

2

Если вы соединитесь elseс for, это может сбить с толку. Я не думаю, что ключевое слово elseбыло отличным выбором для этого синтаксиса, но если вы создадите пару elseс тем, ifкоторый содержит break, вы увидите, что это действительно имеет смысл. elseвряд ли пригодится, если нет предшествующего ifутверждения, и я считаю, что именно поэтому разработчик синтаксиса выбрал ключевое слово.

Позвольте мне продемонстрировать это на человеческом языке.

for каждый человек в группе подозреваемых if кто-то является преступником breakследствия. elseсообщить об ошибке.


1

То, как я думаю об этом, ключ, чтобы рассмотреть значение, continueа не else.

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

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


0
# tested in Python 3.6.4
def buy_fruit(fruits):
    '''I translate the 'else' below into 'if no break' from for loop '''
    for fruit in fruits:
        if 'rotten' in fruit:
            print(f'do not want to buy {fruit}')
            break
    else:  #if no break
        print(f'ready to buy {fruits}')


if __name__ == '__main__':
    a_bag_of_apples = ['golden delicious', 'honeycrisp', 'rotten mcintosh']
    b_bag_of_apples = ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
    buy_fruit(a_bag_of_apples)
    buy_fruit(b_bag_of_apples)

'''
do not want to buy rotten mcintosh
ready to buy ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
'''
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.