我怎样才能理解Python循环的`else`子句?

How can I make sense of the `else` clause of Python loops?

许多python程序员可能不知道while循环和for循环的语法包括可选的else:子句:

1
2
3
4
for val in iterable:
    do_something(val)
else:
    clean_up()

else条款的主体是某种清理行动的良好场所,在循环正常终止时执行:即使用returnbreak跳过else条款退出循环;在continue执行后退出。我之所以知道这一点,仅仅是因为我刚刚(再一次)查阅了它,因为我永远记不清何时执行了else子句。

总是?关于循环的"失败",顾名思义?定期终止?即使循环使用return退出?我不能不抬头就完全确定。

我把一直以来的不确定性归咎于关键字的选择:我发现else在这个语义上非常不通顺。我的问题不是"为什么这个关键字用于这个目的"(我可能会投票结束,虽然只有在阅读了答案和评论之后),而是我如何思考else关键字,以便它的语义有意义,因此我可以记住它?

我相信关于这个问题已经有了相当多的讨论,我可以想象这样的选择是为了与try语句的else:子句保持一致(我也必须查找它),目的是不将它添加到python的保留字列表中。也许选择else的原因将澄清它的功能,使它更令人难忘,但我是在将名称与功能联系起来之后,而不是在历史解释之后。

这个问题的答案,我的问题作为的一个副本,简短地封闭了,包含了许多有趣的背景故事。我的问题有一个不同的焦点(如何将else的特定语义与关键字choice连接起来),但我觉得应该在某个地方有一个指向这个问题的链接。


(这灵感来自@mark tolonen的回答。)

如果条件的计算结果为假,则if语句将运行其else子句。同样,如果条件的计算结果为false,则while循环运行else子句。

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

  • 在正常执行中,while循环反复运行,直到条件的计算结果为false,因此自然退出循环会运行else子句。
  • 当您执行break语句时,在不评估条件的情况下退出循环,因此条件的值不能为false,并且您从不运行else子句。
  • 当您执行一个continue语句时,您将再次评估条件,并按照您在循环迭代开始时通常会做的那样做。因此,如果条件为真,则继续循环,如果条件为假,则运行else子句。
  • 其他退出循环的方法,如return,不评估条件,因此不运行else子句。

for循环的行为方式相同。如果迭代器有更多的元素,只需将条件视为真,否则视为假。


它认为这样更好:else块执行,如果一切都行,我是它在搜索那块preceding for耗尽它。

这意味着正确的上下文exceptionbreakNO,NO,NO return。任何想要控制语句是从hijacks forelsebypassed块造成的。

一个普通的用例是当寻找到了在这项研究中iterable,搜索是为OFF。当一个项目或一"not found"标志是提出了通过以下else/印刷块:

1
2
3
4
5
for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")

a continue不hijack控制从控制for,想继续到elsefor是筋疲力尽。


当执行是在ifelse?当它的状态是FALSE。它是完全相同的while/ else。所以你想whileAS / elseif只是保持运行状态,直到它evaluates其真假。这是一break不变化。它只是一个跳跃(含环和没有评价。如果这是唯一的else评价执行《if/ while状态是FALSE。

for是相似的,除了它的状态是其迭代器返回排气。

continuebreakelse不要执行。这不是他们的功能。含氟环break出口的位置。回到酒店去的continue含顶环,在环的状态是评价。它是评价if/ while法案到FALSE(或没有forITEMS)是executes else和没有其他的方式。


这是什么不均值:

1
2
3
4
5
6
7
for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

这是一个更好的方式在普通模式:写作

1
2
3
4
5
6
7
found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

else条款将不执行,如果有一个returnreturn叶子,因为它是一个函数,是用来。唯一的例外是,你可能是他的finally思路,目的是确保它总是执行。

有没有什么特别continue待办事项这一物。它的原因。当前迭代的循环结束它可能发生到结束的整个回路,回路中的案例和部分是不是由一break端。

try/else是相似的:

1
2
3
4
5
6
7
8
try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...

如果你认为你的循环作为一个结构类似于本(在伪代码):

1
2
3
4
5
6
7
loop:
if condition then

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

它可以做一点更多的镰刀。一环是不只是一if声明这是重复直到false状态。这是一个重要的点。回路检查其状态和它的false湖,因此executes else(就像一个正常的if/else),然后是无环路。

因此,这else只得到通知的执行,当状态是检查。这意味着,如果你退出Body of the loop中执行,例如returnbreakA或A,因为状态不好的情况,else不会被执行。

在另一continue停止当前执行的手,然后跳回到检查状态的一个回路,这就是为什么else可以达到在这个场景。


我对循环的else条款的理解是在我观看雷蒙德·赫廷格的演讲时,他讲了一个他认为应该如何称之为nobreak的故事。看看下面的代码,您认为它会做什么?

1
2
3
4
5
6
for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

你猜是什么?好吧,说nobreak的那部分只有在循环中没有命中break语句时才会被执行。


通常,我想这样:A环结构

1
2
3
4
for item in my_sequence:
    if logic(item):
        do_something(item)
        break

一个焊料if/elif样变数的语句:

1
2
3
4
5
6
7
8
9
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链,它只executes如果不在它的条件,是真的。(或执行一个中断或异常return)如果我不适合本规范环通常使用的第一选择退出的确切原因for: else你张贴这个问题:它是非直观的。


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

如果只考虑简单的if/else语句(没有elif语句),则此方法与此语法结合得很好:

1
2
3
4
if cond:
    # 1
else:
    # 2

推广到:

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

很好地。

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

下面是8thlight博客的一个例子:

在8thlight博客的链接文章中,我们考虑了换行片:在字符串中添加换行符(下面片段中的s变量),使其适合给定的宽度(下面片段中的length变量)。在某种程度上,实现看起来如下(Java):

1
2
3
4
5
6
7
8
String result ="";
if (s.length() > length) {
    result = s.substring(0, length) +"
"
+ s.substring(length);
} else {
    result = s;
}
return result;

下一个测试,目前失败的是:

1
2
3
4
5
6
@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very
long
word"
));
    }

所以我们有条件地工作的代码:当满足特定条件时,会添加一个换行符。我们希望改进代码以处理多个换行符。本文提出的解决方案建议应用(if->while)转换,但作者提出的意见是:

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.

在一个失败的测试环境中,强制对代码进行更多的更改:

1
2
3
4
5
6
7
String result ="";
while (s.length() > length) {
    result += s.substring(0, length) +"
"
;
    s = s.substring(length);
}
result += s;

在TDD中,我们希望尽可能少地编写代码以使测试通过。由于python的语法,以下转换是可能的:

来自:

1
2
3
4
5
6
7
result =""
if len(s) > length:
    result = s[0:length] +"
"

    s = s[length:]
else:
    result += s

到:

1
2
3
4
5
6
7
result =""
while len(s) > length:
    result += s[0:length] +"
"

    s = s[length:]
else:
    result += s


其他人已经解释了while/for...else的机制,python 3语言引用有权威的定义(见while和for),但这里是我的个人助记符fwiw。我想对我来说,关键是把它分成两部分:一部分是理解与循环条件相关的else的含义,另一部分是理解循环控制。

我发现最简单的方法是从了解while...else开始:

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

for...else助记法基本相同:

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

在这两种情况下,只有当没有更多的项目需要处理,并且最后一个项目已按常规处理(即没有breakreturn)时,才会到达else部分。一个continue只是回去看看还有没有其他的东西。本规则的助记法适用于whilefor

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

&ndash;使用"循环返回开始"显然意味着循环的开始,在这里我们检查iterable中是否还有其他项目,就else而言,continue实际上根本不起作用。


在我看来,当您遍历循环的末尾时,else:将触发。

如果你不迭代通过循环的末尾,你会立即停止,这样,else:块就不会运行。如果您continue仍在循环结束时迭代,因为continue只是跳到下一个迭代。它不会停止循环。


else子句看作是循环构造的一部分;break完全脱离了循环构造,因此跳过了else子句。

但实际上,我的心理映射仅仅是模式C/C++模式的"结构化"版本:

1
2
3
4
5
6
7
8
  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

因此,当我遇到for...else或自己编写它时,而不是直接理解它,我会在心里将它转换为对模式的上述理解,然后计算出python语法的哪些部分映射到模式的哪些部分。

(我把"结构化"放在恐吓引号中,因为区别不在于代码是结构化的还是非结构化的,而在于是否有专门用于特定结构的关键字和语法)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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']
'''

如果你想把elsefor结合在一起,可能会让人困惑。我不认为关键字else是这种语法的一个很好的选择,但是如果您将elsebreak配对,您会发现它实际上是有意义的。

让我用人类语言演示一下。

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

如果for循环中没有break,那么else几乎没有用处。


在我看来,关键是要考虑continue的含义,而不是else的含义。

您提到的其他关键字break out of the loop(exit abnormally),而continue没有,它只是跳过了循环中代码块的其余部分。它可以先于循环终止这一事实是偶然的:终止实际上是通过循环条件表达式的计算以正常方式完成的。

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