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

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

提问人:alexis 提问时间:6/5/2016 最后编辑:alexis 更新时间:3/25/2023 访问量:14389

问:

许多 Python 程序员可能不知道 loops 和 loops 的语法包含一个可选子句:whileforelse:

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

子句的正文是某些类型的清理操作的好地方,在循环的正常终止时执行:即,退出循环或跳过子句;在执行它后退出。我之所以知道这一点,只是因为我刚刚(再次)查了一下,因为我永远不记得该条款是什么时候执行的。elsereturnbreakelsecontinueelse

总是?顾名思义,关于循环的“失败”?定期终止?即使循环以 ?如果不查一下,我永远无法完全确定。return

我把我持续的不确定性归咎于关键词的选择:我发现这种语义非常不可信。我的问题不是“为什么这个关键字用于此目的”(我可能会投票关闭,尽管只有在阅读了答案和评论之后),而是我如何考虑 else 关键字,使其语义有意义,因此我可以记住它?else

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

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

python 循环 for-loop while-loop

评论

6赞 Nick is tired 11/17/2020
@alexis Dharman 的编辑是为了让您的问题不基于意见,同时保持问题本身,IMO 的编辑使帖子变得更好(并且不值得关闭)
0赞 alexis 11/17/2020
@Dharman,我感谢您的努力,但您的编辑完全歪曲了问题的意图和内容。请停下来。
3赞 Nick is tired 11/17/2020
“我怎样才能考虑 else 关键字,以便它的语义有意义,因此我可以记住它?”- 具体向你解释我们如何帮助你个人记住作品不是一个有用的问题,Dharman是我撤回对这个问题的接近投票的唯一原因,因为没有它,这个问题是基于意见的else
1赞 alexis 11/17/2020
谢谢你的解释,@nick。然而,Dharman的标题使它成为一个非常不同的问题的重复。如果你确信这太基于意见了,我可以带着你的投票离开。但请不要理会这个问题。
1赞 alexis 11/17/2020
此外,问题在于如何理解这种设计,而不是它的作用。

答:

33赞 Mark Tolonen 6/5/2016 #1

何时执行?当其条件为 false 时。对于 /,它完全相同。因此,您可以将 / 视为一个继续运行其真实条件,直到它计算为 false 为止。A 不会改变这一点。它只是跳出包含循环,没有评估。仅当计算 / 条件为 false 时才执行。ifelsewhileelsewhileelseifbreakelseifwhile

与此类似,只是其错误条件正在耗尽其迭代器。for

continue并且不要执行.那不是他们的功能。退出包含循环。返回到包含循环的顶部,在那里评估循环条件。执行的是评估/为假(或没有更多项目)的行为,没有其他方式。breakelsebreakcontinueifwhileforelse

评论

1赞 alexis 6/5/2016
你说的听起来很有道理,但是将三个终止条件混为一谈,“直到 [条件] 为 False 或中断/继续”是错误的:至关重要的是,如果循环退出(或正常),则该子句被执行,但如果我们退出 .这些微妙之处就是为什么我试图真正弄清楚什么能抓住什么,什么不能抓住。elsecontinuebreakelse
4赞 Mark Tolonen 6/6/2016
@alexis是的,我需要澄清一下。编辑。continue 不会执行 else,但会返回到循环的顶部,然后计算结果可能为 false。
24赞 Alex Hall 6/5/2016 #2

这就是它的本质含义:

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

这是这种常见模式的更好编写方式:

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

如果存在 because 离开函数,则不会执行该子句。您可能想到的唯一例外是 ,其目的是确保它始终被执行。elsereturnreturnfinally

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

try/else是相似的:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...
20赞 Keiwan 6/5/2016 #3

如果你把你的循环想象成一个类似于这样的结构(有点伪代码):

loop:
if condition then

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

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

因此,请注意,当检查条件时,将执行唯一的 get。这意味着,如果在执行过程中使用 a 或 a 退出循环主体,因为没有再次检查条件,则不会执行该案例。elsereturnbreakelse

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

评论

0赞 Bergi 6/7/2016
我非常喜欢这个答案,但你可以简化一下:省略标签,只放在正文里面。甚至可能通过将 the 与标签放在同一行上来缩进,它突然看起来非常像 oriignal。endgoto loopifif
0赞 Keiwan 6/7/2016
@Bergi 是的,我认为这更清楚了,谢谢。
36赞 Moses Koledoye 6/5/2016 #4

最好这样想:如果前一个区块一切顺利,以至于耗尽,则该区块将始终被执行。elsefor

在这种情况下,权利将意味着不,不不。任何劫持控制权的语句都会导致该块被绕过。exceptionbreakreturnforelse


在 中搜索项目时会发现一个常见的用例,在找到项目时取消搜索,或者通过以下块引发/打印标志:iterable"not found"else

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

A 不会劫持 的控制权,因此控制权将在 耗尽后继续进行。continueforelsefor

评论

22赞 alexis 6/5/2016
听起来很不错...但是,当事情不顺利时,你会期望执行一个条款,不是吗?我又开始糊涂了......else
0赞 Tadhg McDonald-Jensen 6/6/2016
我必须不同意你的观点“从技术上讲,它并不[在语义上与其他所有条件相似]”,因为 是在 for 循环中没有一个条件计算为 True 时运行的,正如我在回答中所证明的那样elseelse
0赞 Moses Koledoye 6/6/2016
@TadhgMcDonald-Jensen 您还可以在 .因此,如何破坏的问题取决于用例。Falsefor
0赞 alexis 6/6/2016
没错,我正在寻找一种方法来以某种方式将发生的事情与“else”的英文含义联系起来(这确实反映在 python 中的其他用法中)。你提供了一个很好的直观总结,说明了什么,@Moses,但没有说明我们如何将这种行为与“其他”联系起来。如果使用不同的关键字(例如,如本相关问题的答案中所述),则更容易理解。elseelsenobreak
1赞 Mark Tolonen 6/6/2016
这真的与“事情进展顺利”无关。else 纯粹在 / 条件计算结果为 false 或缺少项目时执行。 存在包含循环(在 之后)。 返回并再次评估环路条件。ifwhileforbreakelsecontinue
8赞 Tadhg McDonald-Jensen 6/6/2016 #5

通常我倾向于认为循环结构是这样的:

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

在这种情况下,for 循环上的语句的工作方式与 s 链上的语句完全相同,它仅在计算结果为 True 之前的条件均未满足时执行。(或中断执行或异常)如果我的循环不符合这个规范,通常我会选择不使用,因为你发布这个问题的确切原因:它不直观。elseelseelifreturnfor: else

评论

1赞 alexis 6/6/2016
右。但是一个循环运行多次,所以你不清楚如何将其应用于 for 循环。你能澄清一下吗?
0赞 Tadhg McDonald-Jensen 6/10/2016
@alexis我已经重新回答了,但我认为现在更清楚了。
218赞 drawoc 6/6/2016 #6

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

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

  • 在正常执行中,while 循环会反复运行,直到条件的计算结果为 false,因此自然而然地退出循环会运行 else 子句。
  • 当您执行语句时,您将退出循环而不计算条件,因此该条件的计算结果不能为 false,并且您永远不会运行 else 子句。break
  • 当您执行语句时,您将再次评估该条件,并完全按照您在循环迭代开始时通常执行的操作。 因此,如果条件为 true,则继续循环,但如果条件为 false,则运行 else 子句。continue
  • 退出循环的其他方法(如 )不计算条件,因此不运行 else 子句。return

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

评论

8赞 Nomenator 6/8/2016
这是一个很好的答案。把你的循环看作是一系列的 elif 语句,else 行为将暴露其自然逻辑。
1赞 alexis 6/8/2016
我也喜欢这个答案,但它并没有与一系列陈述进行类比。有一个答案是肯定的,它有一个净赞成票。elif
3赞 Tadhg McDonald-Jensen 6/15/2016
不完全是,while 循环可以让条件在它 s 之前满足 False,在这种情况下,它不会运行,但条件是 False。与循环类似,它可以在最后一个元素上。breakelseforbreak
4赞 user1084944 6/6/2016 #7

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

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

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

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

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

评论

1赞 alexis 6/6/2016
哪里是?如果你的意思是标签代表代理或,我相信你完全倒过来了。elsedone:else:
0赞 zwol 6/7/2016
@alexis “else”代码将填写“...”代码紧挨着标签。总体对应关系也许最好这样说:Python 具有 -on-loop 结构,因此您可以在没有 .done:elsegoto
0赞 alexis 6/7/2016
还有其他方法可以执行此控制流模式,例如通过设置标志。这就是避免的。else
7赞 Fabian Fagerholm 6/6/2016 #8

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

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

当你有更多的物品时,做一些事情,否则如果你用完了,就这样做

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

对于每个项目,做一些事情,但如果你用完了,就这样做

在这两种情况下,只有当没有更多项目需要处理,并且最后一个项目已以常规方式处理(即无或)时,才会到达该部分。A 只是回去看看是否还有更多项目。我对这些规则的助记符适用于以下两个方面:elsebreakreturncontinuewhilefor

当中断返回时,没有其他事情可做,当我说继续时,
这对你来说就是“循环回到开始”

– “循环回到起点”的意思,显然,循环的开始,我们检查可迭代对象中是否还有更多项目,所以就 而言,实际上根本没有作用。elsecontinue

评论

4赞 supercat 6/7/2016
我建议可以通过说 for/else 循环的通常目的是检查项目,直到你找到你要找的东西并想停止,或者你用完了项目来增强它。“else”的存在是为了处理“你用完了项目(没有找到你要找的东西)”部分。
0赞 Fabian Fagerholm 6/7/2016
@supercat:可能是,但我不知道最常见的用途是什么。当您简单地完成所有项目时,也可以用来做某事。示例包括编写日志条目、更新用户界面或发出已完成的其他进程的信号。任何事情,真的。此外,某些代码段的“成功”情况以内部循环结尾,并且用于处理在迭代过程中找不到任何合适项的“错误”情况(也许这就是您想到的?elsebreakelse
1赞 supercat 6/7/2016
我想到的案例恰恰是成功的案例以“中断”结束,而“其他”处理缺乏成功的情况。如果循环中没有“中断”,则“else”代码也可以简单地跟随循环作为封闭块的一部分。
0赞 Fabian Fagerholm 6/7/2016
除非您需要区分循环不间断地遍历所有可迭代项目的情况(这是一个成功的情况)和没有的情况。然后,您必须将“最终确定”代码放入循环的块中,或者使用其他方式跟踪结果。我基本上同意,我只是说我不知道人们如何使用这个功能,因此我想避免假设是“处理成功案例”场景还是“处理不成功案例”场景更常见。但是你说得好,所以评论点赞!elseelseelse
16赞 Nasser Al-Shawwa 6/6/2016 #9

我对循环从句的困惑时刻是当我在观看雷蒙德·赫廷格(Raymond Hettinger)的演讲时,他讲述了一个关于他认为应该如何称呼它的故事。看看下面的代码,你认为它会做什么?elsenobreak

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

你猜它有什么作用?好吧,只有当循环中没有命中语句时,才会执行说的部分。nobreakbreak

7赞 BartoszKP 6/7/2016 #10

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

如果您只考虑简单(无)语句,则此方法与此语法很好地结合在一起:if/elseelif

if cond:
    # 1
else:
    # 2

概括为:

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

好。

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


以下是 8thlight 博客中的一个示例:

在 8thlight 博客的链接文章中,考虑了 Word Wrap 套路:向字符串添加换行符(下面代码段中的变量)以使其适合给定的宽度(下面代码段中的变量)。在某一点上,实现如下所示(Java):slength

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) 转换,但作者评论说:

虽然循环不能有子句,所以我们需要通过在路径中做更少的操作来消除路径。同样,这是一个重构。elseelseif

这迫使在一个失败的测试的上下文中对代码进行更多更改:

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赞 Winston Ewert 6/7/2016 #11

在我看来,当你迭代到循环的末尾时,就会触发它。else:

如果你或你没有迭代到循环的末尾,你就会立即停止,因此块不会运行。如果你仍然迭代到循环的末尾,因为继续只是跳到下一个迭代。它不会停止循环。breakreturnraiseelse:continue

评论

1赞 alexis 6/8/2016
我喜欢这样,我认为你正在做一些事情。这与循环关键字之前的糟糕过去如何实现循环有点联系。(即:检查放在循环的底部,顶部表示成功。但这是得票最多的答案的较短版本......goto
0赞 Winston Ewert 6/8/2016
@alexis,主观的,但我发现我的表达方式更容易思考。
0赞 alexis 6/8/2016
其实我同意。如果只是因为它更辣。
1赞 Bob Sammers 6/8/2016 #12

我的想法是,关键是要考虑 的含义而不是 .continueelse

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

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

2赞 user5538922 5/31/2018 #13

如果与 配对,可能会造成混淆。我不认为关键字是这种语法的绝佳选择,但是如果您与 which contains 配对,您可以看到它实际上是有道理的。 如果没有前面的语句,则几乎没有用处,我相信这就是语法设计者选择关键字的原因。elseforelseelseifbreakelseif

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

for每个人在一组嫌疑人中,任何人都是调查的罪犯。 报告失败。ifbreakelse

-1赞 Down the Stream 7/10/2018 #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']
'''
0赞 Géry Ogam 2/3/2022 #15

带有子句的 while 语句else

while condition:
    iteration
else:
    conclusion

正好等同于

while True:
    if not condition:
        conclusion
        break
    iteration

带有子句的 for 语句else

for item in iterable:
    iteration
else:
    conclusion

正好等同于

iterator = iter(iterable)
while True:
    try:
        item = next(iterator)
    except StopIteration:
        conclusion
        break
    iteration

它有助于理解迭代语句中 or 语句的效果。breakcontinue

注意。— 对于不带子句的 and 语句,将结论语句替换为等效代码中的语句。whileforelsepass

评论

1赞 Beni Cherniavsky-Paskin 3/24/2023
循环的一个微妙之处是它只在执行时捕获引发的 。如果块内有其他东西提出,该异常将退出循环(并且不会执行)。但是要准确地写出来是很混乱的(不求助于while..否则)。forStopIterationnext(iterator)iterationStopIterationconclusion
1赞 Géry Ogam 3/25/2023
@BeniCherniavsky-Paskin 非常感谢你,我已经修复了代码(希望如此)。现在实际上更容易阅读。