我如何理解Python循环的`else`子句?

问题:我如何理解Python循环的`else`子句?

许多Python程序员可能没有意识到while循环和for循环的语法包含一个可选else:子句:

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

else子句的主体是执行某些类型的清理操作的好地方,并在循环的正常终止时执行:即,使用returnbreak跳过else子句退出循环;continue执行后退出。我知道这只是因为我只是看着它(再次),因为我永远记得else子句被执行。

总是?顾名思义,在循环的“失败”上?定期终止吗?即使循环退出return?我永远不能完全确定,不查它。

我将不确定性归咎于关键字的选择:我发现else这种语义难以置信。我的问题不是“为什么要为此目的使用该关键字”(虽然只有在阅读了答案和评论之后,我才可能会投票关闭关键字),但我如何考虑该else关键字,以便其语义有意义,我因此可以记住吗?

我敢肯定,对此进行了大量讨论,并且我可以想象做出这一选择是为了与try语句的else:子句(我也必须查找)保持一致,并且其目标是不添加到该语句的列表中。 Python的保留字。也许选择的原因else将阐明其功能并使之更加令人难忘,但我是将名称与功能联系在一起,而不是出于历史解释本身。

这个问题的答案(我的问题作为与之重复的问题简短地结束了)包含很多有趣的背景故事。我的问题有不同的侧重点(如何将特定语义else与关键字选择联系起来),但是我觉得应该在某个地方链接到该问题。

Many Python programmers are probably unaware that the syntax of while loops and for loops includes an optional else: clause:

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

The body of the else clause is a good place for certain kinds of clean-up actions, and is executed on normal termination of the loop: I.e., exiting the loop with return or break skips the else clause; exiting after a continue executes it. I know this only because I just looked it up (yet again), because I can never remember when the else clause is executed.

Always? On “failure” of the loop, as the name suggests? On regular termination? Even if the loop is exited with return? I can never be entirely sure without looking it up.

I blame my persisting uncertainty on the choice of keyword: I find else incredibly unmnemonic for this semantics. My question is not “why is this keyword used for this purpose” (which I would probably vote to close, though only after reading the answers and comments), but how can I think about the else keyword so that its semantics make sense, and I can therefore remember it?

I’m sure there was a fair amount of discussion about this, and I can imagine that the choice was made for consistency with the try statement’s else: clause (which I also have to look up), and with the goal of not adding to the list of Python’s reserved words. Perhaps the reasons for choosing else will clarify its function and make it more memorable, but I’m after connecting name to function, not after historical explanation per se.

The answers to this question, which my question was briefly closed as a duplicate of, contain a lot of interesting back story. My question has a different focus (how to connect the specific semantics of else with the keyword choice), but I feel there should be a link to this question somewhere.


回答 0

(这受@Mark Tolonen的回答启发。)

一个if语句运行它else,如果它的条件计算为false条款。同样,while如果条件的条件评估为false ,则循环将运行else子句。

此规则与您描述的行为匹配:

  • 在正常执行中,while循环会重复运行直到条件评估为false为止,因此自然退出循环将运行else子句。
  • 当执行一条break语句时,您退出循环而不评估条件,因此条件不能评估为false,并且您从不运行else子句。
  • 当您执行一条continue语句时,您将再次评估条件,并按照循环迭代开始时的正常方式进行操作。因此,如果条件为true,则继续循环,但是如果条件为false,则运行else子句。
  • 其他退出循环的方法(例如)return不会评估条件,因此不会运行else子句。

for循环的行为方式相同。如果迭代器具有更多元素,则仅将条件视为true,否则将其视为false。

(This is inspired by @Mark Tolonen’s answer.)

An if statement runs its else clause if its condition evaluates to false. Identically, a while loop runs the else clause if its condition evaluates to false.

This rule matches the behavior you described:

  • In normal execution, the while loop repeatedly runs until the condition evaluates to false, and therefore naturally exiting the loop runs the else clause.
  • When you execute a break statement, you exit out of the loop without evaluating the condition, so the condition cannot evaluate to false and you never run the else clause.
  • When you execute a continue statement, you evaluate the condition again, and do exactly what you normally would at the beginning of a loop iteration. So, if the condition is true, you keep looping, but if it is false you run the else clause.
  • Other methods of exiting the loop, such as return, do not evaluate the condition and therefore do not run the else clause.

for loops behave the same way. Just consider the condition as true if the iterator has more elements, or false otherwise.


回答 1

最好这样考虑:如果前一个块中的所有内容都正确,以至于用尽,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,因此控制权将elsefor用尽后继续进行。

Better to think of it this way: The else block will always be executed if everything goes right in the preceding for block such that it reaches exhaustion.

Right in this context will mean no exception, no break, no return. Any statement that hijacks control from for will cause the else block to be bypassed.


A common use case is found when searching for an item in an iterable, for which the search is either called off when the item is found or a "not found" flag is raised/printed via the following else block:

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

A continue does not hijack control from for, so control will proceed to the else after the for is exhausted.


回答 2

什么时候if执行else?当其条件为假时。正是在相同while/ else。因此,您可以将while/ else视为if一直运行其真实条件直到其评估为false的。A break不会改变这一点。它只是跳出包含循环而没有评估。该else如果仅执行评估if/ while条件为假。

for是相似的,除了它的假条件被消耗其迭代器。

continue并且break不要执行else。那不是他们的功能。在break退出循环含有。将continue返回到循环包含,其中,所述循环条件被评估的顶部。评估if/ while将错误(或for没有更多项目)执行的动作是else没有其他方法的。

When does an if execute an else? When its condition is false. It is exactly the same for the while/else. So you can think of while/else as just an if that keeps running its true condition until it evaluates false. A break doesn’t change that. It just jumps out of the containing loop with no evaluation. The else is only executed if evaluating the if/while condition is false.

The for is similar, except its false condition is exhausting its iterator.

continue and break don’t execute else. That isn’t their function. The break exits the containing loop. The continue goes back to the top of the containing loop, where the loop condition is evaluated. It is the act of evaluating if/while to false (or for has no more items) that executes else and no other way.


回答 3

这实际上是什么意思:

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,这意味着它会这样做。您可能想到的唯一exceptions是finally,其目的是确保始终执行该exceptions。

continue与这件事没有什么特别的关系。它导致循环的当前迭代结束,而这可能恰好结束了整个循环,显然,在这种情况下,循环并未以结束break

try/else 很相似:

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

This is what it essentially means:

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

It’s a nicer way of writing of this common pattern:

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

The else clause will not be executed if there is a return because return leaves the function, as it is meant to. The only exception to that which you may be thinking of is finally, whose purpose is to be sure that it is always executed.

continue has nothing special to do with this matter. It causes the current iteration of the loop to end which may happen to end the entire loop, and clearly in that case the loop wasn’t ended by a break.

try/else is similar:

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

回答 4

如果您将循环视为与此类似的结构(某种伪代码):

loop:
if condition then

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

这可能更有意义。循环本质上只是if重复一个语句,直到条件为为止false。这是重要的一点。循环检查其条件并查看是否为false,因此执行else(就像正常if/else),然后完成循环。

因此请注意,else 检查条件后,将执行唯一的获取。这意味着,如果在执行过程中使用a return或a 退出循环的主体break,因为不会再次检查条件,else则不会执行大小写。

continue另一方面,A 停止当前执行,然后跳回以再次检查循环条件,这就是else在这种情况下可以达到的原因。

If you think of your loops as a structure similar to this (somewhat pseudo-code):

loop:
if condition then

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

it might make a little bit more sense. A loop is essentially just an if statement that is repeated until the condition is false. And this is the important point. The loop checks its condition and sees that it’s false, thus executes the else (just like a normal if/else) and then the loop is done.

So notice that the else only get’s executed when the condition is checked. That means that if you exit the body of the loop in the middle of execution with for example a return or a break, since the condition is not checked again, the else case won’t be executed.

A continue on the other hand stops the current execution and then jumps back to check the condition of the loop again, which is why the else can be reached in this scenario.


回答 5

我对循环的else子句的了解是在我观看Raymond Hettinger的演讲时,他讲了一个有关他认为应该如何称呼它的故事nobreak。看下面的代码,您认为它将做什么?

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

您会怎么做?好吧,nobreak只有在break语句未在循环中命中的情况下,才会执行所说的部分。

My gotcha moment with the loop’s else clause was when I was watching a talk by Raymond Hettinger, who told a story about how he thought it should have been called nobreak. Take a look at the following code, what do you think it would do?

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

What would you guess it does? Well, the part that says nobreak would only be executed if a break statement wasn’t hit in the loop.


回答 6

通常,我倾向于想到这样的循环结构:

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])

在这种情况下,elsefor循环上的语句的工作原理elseelifs 链上的语句完全相同,仅在条件为True之前没有条件时才执行。(或者用return异常中断执行)如果我的循环通常不符合此规范,则出于for: else您发布此问题的确切原因,我选择不使用:这是不直观的。

Usually I tend to think of a loop structure like this:

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

To be a lot like a variable number of if/elif statements:

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])

In this case the else statement on the for loop works exactly like the else statement on the chain of elifs, it only executes if none of the conditions before it evaluate to True. (or break execution with return or an exception) If my loop does not fit this specification usually I choose to opt out of using for: else for the exact reason you posted this question: it is non-intuitive.


回答 7

其他人已经解释了的机制while/for...else,并且Python 3语言参考具有权威性的定义(请参见whilefor),但这是我的个人助记符FWIW。我想对我来说关键在于将其分解为两部分:一个部分用于理解else与循环条件相关的的含义,另一部分用于理解循环控制。

我发现从理解开始是最容易的while...else

while你有更多物品,做东西,else如果用完了,这样做

for...else助记符是基本相同的:

for每个项目,做一些事情,但是else如果用完了,请这样做

在这两种情况下,else仅在没有更多要处理的项目并且以常规方式(即,否breakreturn)处理了最后一个项目时才到达该零件。一个continue刚刚回到如果有任何更多的项目看。这些规则的助记符适用于whilefor

break荷兰国际集团或returnING,没有什么是else做的,
当我说continue,这是“环回至开始”为您服务

–显然,“循环返回开始”的意思是循环的开始,在该循环中,我们检查可迭代项中是否还有其他项,else就而言,它continue实际上根本没有作用。

Others have already explained the mechanics of while/for...else, and the Python 3 language reference has the authoritative definition (see while and for), but here is my personal mnemonic, FWIW. I guess the key for me has been to break this down into two parts: one for understanding the meaning of the else in relation to the loop conditional, and one for understanding loop control.

I find it’s easiest to start by understanding while...else:

while you have more items, do stuff, else if you run out, do this

The for...else mnemonic is basically the same:

for every item, do stuff, but else if you run out, do this

In both cases, the else part is only reached once there are no more items to process, and the last item has been processed in a regular manner (i.e. no break or return). A continue just goes back and sees if there are any more items. My mnemonic for these rules applies to both while and for:

when breaking or returning, there’s nothing else to do,
and when I say continue, that’s “loop back to start” for you

– with “loop back to start” meaning, obviously, the start of the loop where we check whether there are any more items in the iterable, so as far as the else is concerned, continue really plays no role at all.


回答 8

测试驱动的开发(TDD)中,当使用转换优先级前提范式时,您将循环视为条件语句的概括。

如果仅考虑简单的if/else(no elif)语句,则此方法可以与以下语法很好地结合:

if cond:
    # 1
else:
    # 2

概括为:

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

很好

用其他语言,TDD从单个案例到具有集合案例的步骤需要更多的重构。


这是8thlight博客的示例:

在8thlight博客的链接文章中,考虑了自动换行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

In Test-driven development (TDD), when using the Transformation Priority Premise paradigm, you treat loops as a generalization of conditional statements.

This approach combines well with this syntax, if you consider only simple if/else (no elif) statements:

if cond:
    # 1
else:
    # 2

generalizes to:

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

nicely.

In other languages, TDD steps from a single case to cases with collections require more refactoring.


Here is an example from 8thlight blog:

In the linked article at 8thlight blog, the Word Wrap kata is considered: adding line breaks to strings (the s variable in the snippets below) to make them fit a given width (the length variable in the snippets below). At one point the implementation looks as follows (Java):

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

and the next test, that currently fails is:

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

So we have code that works conditionally: when a particular condition is met, a line break is added. We want to improve the code to handle multiple line breaks. The solution presented in the article proposes to apply the (if->while) transformation, however the author makes a comment that:

While loops can’t have else clauses, so we need to eliminate the else path by doing less in the if path. Again, this is a refactoring.

which forces to do more changes to the code in the context of one failing test:

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

In TDD we want to write as less code as possible to make tests pass. Thanks to Python’s syntax the following transformation is possible:

from:

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

to:

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

回答 9

else:当您遍历循环结束时,就会触发我的观察方式。

如果你break或者return或者raise你没有过去的迭代循环的结尾,你停下immeadiately,因而else:块将不会运行。如果您continue仍然循环结束,因为continue会跳到下一个迭代。它不会停止循环。

The way I see it, else: fires when you iterate past the end of the loop.

If you break or return or raise you don’t iterate past the end of loop, you stop immeadiately, and thus the else: block won’t run. If you continue you still iterate past the end of loop, since continue just skips to the next iteration. It doesn’t stop the loop.


回答 10

else子句视为循环构造的一部分;break完全脱离循环构造,从而跳过该else子句。

但实际上,我的思维导图只是它是模式C / C ++模式的“结构化”版本:

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

因此,当我for...else自己遇到或编写它时,而不是直接了解它,我会在思维上将其转化为对模式的上述理解,然后确定python语法的哪些部分映射到模式的哪些部分。

(我将“结构化”用惊吓语括起来,因为区别不在于代码是结构化的还是非结构化的,而仅仅是是否有专门针对特定结构的关键字和语法)

Think of the else clause as being part of the loop construct; break breaks out of the loop construct entirely, and thus skips the else clause.

But really, my mental mapping is simply that it’s the ‘structured’ version of the pattern C/C++ pattern:

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

So when I encounter for...else or write it myself, rather than understand it directly, I mentally translate it into the above understanding of the pattern and then work out which parts of the python syntax map to which parts of the pattern.

(I put ‘structured’ in scare quotes because the difference is not whether the code is structured or unstructured, but merely whether there are keywords and grammar dedicated to the particular structure)


回答 11

如果else与配对for,可能会造成混淆。我认为关键字不是else此语法的理想选择,但如果elseifcontains 配对,则break可以看到它确实有意义。else如果没有前面的if语句,则几乎没有用,我相信这就是语法设计者选择关键字的原因。

让我用人类语言来演示它。

for犯罪嫌疑if人组中的每个人都是犯罪分子 break进行调查。else报告失败。

If you pair else with for, it could be confusing. I don’t think the keyword else was a great choice for this syntax, but if you pair else with if which contains break, you can see it actually makes sense. else is barely useful if there is no preceding if statement and I believe this is why the syntax designer chose the keyword.

Let me demonstrate it in human language.

for each person in a group of suspects if anyone is the criminal break the investigation. else report failure.


回答 12

我的思考方式,关键是要考虑continue而不是的含义else

您提到的其他关键字会跳出循环(异常退出),而continue不会跳出循环,只会跳过循环内代码块的其余部分。它可以在循环终止之前发生的事实是偶然的:终止实际上是通过评估循环条件表达式以正常方式完成的。

然后,您只需要记住该else子句是在正常循环终止后执行的。

The way I think about it, the key is to consider the meaning of continue rather than else.

The other keywords you mention break out of the loop (exit abnormally) whilst continue does not, it just skips the remainder of the code block inside the loop. The fact that it can precede loop termination is incidental: the termination is actually done in the normal way by evaluation of the loop conditional expression.

Then you just need to remember that the else clause is executed after normal loop termination.


回答 13

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