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


191

许多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与关键字选择联系起来),但是我觉得应该在某个地方链接到该问题。


23
怎么样“如果还有东西要迭代...否则”
OneCricketeer

4
我认为您在写完这个问题后就可以记住它了:)
贾斯珀(Jasper)2013年

11
else基本上是指“如果继续条件失败”。在传统的for循环中,延续条件通常为i < 42,在这种情况下,您可以将该部分视为if i < 42; execute the loop body; else; do that other thing
njzk2

1
这都是真的,我特别喜欢drawoc的答案,但是要考虑的另一件事是,else是一个可用的关键字,在语法上也有一定道理。您可能知道try / except,也许还有try / except / finally,但它还有其他内容 - 如果未发生异常,请运行此代码。顺便说一句,与在try子句下插入此代码不一样-狭义定位时最好使用异常处理。因此,虽然从概念上讲是有意义的(根据此处的许多答案),但我认为它也是在起作用的关键字重用- 在某些条件下运行它
JL Peyret

1
@Falanwe,由退出代码时会有所不同break。规范的用例是循环搜索某事物,并在找到它时中断。该else如果没有找到时,才会执行。
亚历克西斯

Answers:


212

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

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

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

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

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


8
这是一个非常好的答案。将您的循环视为一系列elif语句,否则else行为将暴露其自然逻辑。
命名者

1
我也喜欢这个答案,但是它并没有与一系列elif陈述相提并论。有一个答案可以,并且有一个净投票数。
亚历克西斯

2
不太准确,while循环可能使条件在它之前满足False break,在这种情况下,else该条件将不会运行,但条件为False。与for循环类似,它可以break位于最后一个元素上。
塔德格·麦当劳-詹森

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,因此控制权将elsefor用尽后继续进行。


20
听起来非常好……但是您希望else当情况正确时执行一个子句,不是吗?我已经再次感到困惑...
Alexis

我必须在“技术上,它[在本质上并不彼此相似else]” 上不同意您的意见,因为else当for循环中的任何条件都没有评估为True时,就运行了,正如我在回答中所
Tadhg McDonald- Jensen

@ TadhgMcDonald-Jensen您也可以打破上的循环False。那么该怎么问题for打破依赖于使用情况。
Moses Koledoye

没错,我正在寻求一种方法,以某种方式将发生的事与“ else”的英语含义相关联(这确实反映else在python的其他用法中)。您提供了else@Moses的功能的直观概述,但没有提供我们如何将这种行为与“ else”关联的摘要。如果使用了不同的关键字(例如,nobreak如对相关问题的回答中所述),则将更容易理解。
亚历克西斯

1
它确实与“一切正常”无关。当if/ while条件评估为false或for项目不存在时,else纯粹执行。 break存在包含循环(在之后else)。 continue返回并再次评估循环条件。
Mark Tolonen

31

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

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

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


1
您说的听起来很明智,但是将三个终止条件放在一起,“直到[条件]为False或中断/继续”,这是错误的:最重要的是,else如果用continue(或通常)退出循环,则执行该子句,但是如果我们以退出,则不会break。这些微妙之处就是为什么我要尝试真正else捕获哪些捕获和不捕获的原因。
亚历克西斯

4
@alexis是的,我需要在那里澄清。编辑。继续不会执行else,但是会返回到循环的顶部,然后循环的结果可能为false。
Mark Tolonen

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与这件事没有什么特别的关系。它导致循环的当前迭代结束,而这可能恰好结束了整个循环,显然,在这种情况下,循环并未以结束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则不会执行大小写。

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


我非常喜欢这个答案,但是您可以简化一下:省略end标签,然后将其goto loop放在if体内。甚至可能将if标签放在与标签相同的行上,从而显得有些奇怪,并且突然看起来很像原始标签。
Bergi

@Bergi是的,谢谢。
Keiwan

15

我对循环的else子句的了解是在我观看Raymond Hettinger的演讲时,他讲了一个有关他认为应该如何称呼它的故事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])

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


对。但是循环会运行多次,因此尚不清楚将其应用于for循环的含义。你能澄清一下吗?
亚历克西斯

@alexis我已重做我的答案,我认为现在已经很清楚了。
塔德麦当劳-詹森

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实际上根本没有作用。


4
我建议可以通过说for / else循环的通常目的是检查项目,直到发现所需的内容并要停止,或者您用完了这些内容来增强它。存在“其他”来处理“您的项目用完了(未找到所需的内容)”部分。
超级猫

@supercat:可以,但是我不知道最常见的用途是什么。该else也可以用来做,当你只是与所有项目完成的东西。示例包括编写日志条目,更新用户界面或用信号通知您已完成的其他过程。真的。另外,一些代码的“成功”用例以break循环内部结尾,并且else用于处理“错误”用例,在这种情况下您在迭代过程中找不到任何合适的项(也许正是您所想的那样)。的?)。
Fabian Fagerholm '16

1
我在想的情况恰好是成功的案例以“中断”结尾,而“其他”则解决了缺乏成功的案例。如果循环内没有“中断”,那么“其他”代码也可以简单地跟随循环作为封闭块的一部分。
超级猫

除非您需要区分循环不间断地遍历所有可迭代项的情况(那是成功的情况)和没有进行循环的情况。然后,您必须将“ finalising”代码放入循环的else块中,或者使用其他方式跟踪结果。我基本上同意,我只是说我不知道​​人们如何使用此功能,因此,我想避免做出“ else处理成功案例”场景还是“ else处理不成功案例”场景更为普遍的假设。但是您有一个很好的观点,因此评论已被评论!
Fabian Fagerholm '16

7

测试驱动的开发(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

6

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

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


1
我喜欢那样,我认为您正在做某事。它与循环关键字在糟糕的过去曾经如何实现循环有一点联系。(即:检查放在循环的底部goto成功的顶部​​。)但这是投票最多的答案的简短版本……
Alexis,2016年

@alexis,主观的,但是我发现表达它的方式更容易思考。
Winston Ewert

实际上我同意。如果仅仅是因为它的价格更高。
亚历克西斯

4

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

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

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

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

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


1
在哪里else?如果您的意思是done:标签要代表代理或else:,我相信您的标签就完全倒退了。
Alexis

@alexis“ else”代码将在标签的“ ...”中填充done:。总体上的对应关系最好这样说:Python具有else-on-loop构造,因此您可以不用来表示此控制流模式goto
zwol

还有其他执行此控制流模式的方法,例如,通过设置标志。那就是else避免的事情。
Alexis

2

如果else与配对for,可能会造成混淆。我认为关键字不是else此语法的理想选择,但如果elseifcontains 配对,则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']
'''
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.