Подтвердить что ты не робот

Как я могу осмыслить предложение `else` петель Python?

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

В этом контексте будет означать не exception, no break, no 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.

Ответ 3

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

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

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

Ответ 4

Это то, что по существу означает:

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 не имеет ничего общего с этим вопросом. Это приводит к завершению текущей итерации цикла, которая может закончиться целым циклом, и ясно, что в этом случае цикл не заканчивался на break.

try/else аналогичен:

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

Ответ 5

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

loop:
if condition then

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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 в цепочке elif s, он выполняется только в том случае, если ни одно из условий, прежде чем оно будет оценивать значение True. (или прекратить выполнение с помощью return или исключения). Если мой цикл не соответствует этой спецификации, я обычно предпочитаю отказаться от использования for: else по той причине, что вы разместили этот вопрос: он не интуитивно понятен.

Ответ 8

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

Мне легче начинать с понимания while...else:

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

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

for каждый элемент, делайте что-нибудь, но else, если вы закончите, сделайте это

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

когда break ing или return ing, ничего else делать,
и когда я скажу continue, что для вас "loop back to start"

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

Ответ 9

В Test-driven development (TDD) при использовании Трансформация Priority Premise, вы рассматриваете циклы как обобщение условных утверждений.

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

if cond:
    # 1
else:
    # 2

обобщает на:

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

хорошо.

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


Вот пример из 8thlight blog:

В связанной статье в блоге 8thlight рассматривается статья Wrap kata: добавление разрывов строк к строкам (переменная 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

Ответ 10

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

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

Ответ 11

Вспомните предложение else как часть конструкции цикла; break полностью вырывается из конструкции цикла и, таким образом, пропускает предложение else.

Но на самом деле мое ментальное сопоставление просто состоит в том, что это "структурированная" версия шаблона шаблона C/С++:

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

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

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

Ответ 12

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

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

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

Ответ 13

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

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

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


else вряд ли полезно, если в любом случае не было break цикла for.

Ответ 14

# 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']
'''