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

许多Python程序员可能不知道while循环和for循环的语法包含一个可选的else:语句:

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

else子句的主体对于某些types的清理操作是一个好的地方,并且在循环的正常结束时执行:即,用returnbreak退出循环跳过else子句; continue执行后退出。 我知道这只是因为我只是查了一遍(再一次),因为我永远不会记得什么时候执行else子句。

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

我责怪我对关键词的select持续的不确定性:我发现这个语义的else难以置信的无法理解。 我的问题不是“为什么这个关键字用于这个目的”(我可能会投票结束,虽然只有在阅读答案和意见后),但我怎么能考虑else关键字,使其语义是有道理的,我可以记得吗?

我确信这里有相当多的讨论,我可以想象,这个select是为了与try语句的else:语句保持一致(我也必须查看),并且目标是不添加到Python的保留字列表。 也许selectelse的理由会澄清它的function,使它更加令人难忘,但是我把名字连接到function之后,而不是经过历史的解释。

这个问题的答案,我的问题被简短地重复了一遍,包含了很多有趣的背景故事。 我的问题有一个不同的焦点(如何连接else关键字select的具体语义),但我觉得应该有一个链接到这个问题的地方。

(这是来自@Mark Tolonen的回答。)

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

此规则与您所描述的行为相符:

  • 在正常执行中,while循环重复运行,直到条件计算结果为false,因此自然退出循环运行else子句。
  • 当你执行一个break语句时,你退出循环而不计算条件,所以条件不能计算为false,你永远不会运行else子句。
  • 当你执行一个continue语句的时候,你再次评估这个条件,并且在循环迭代开始的时候完成你通常的操作。 所以,如果条件是真的,你继续循环,但如果它是假的,你运行else子句。
  • 退出循环的其他方法(如return )不会评估条件,因此不会运行else子句。

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

最好这样来思考:如果块中的所有内容都正确地运行 ,那么else总是会被执行,从而使其耗尽。

在这种情况下,就意味着没有exception ,不会break ,不会有任何return 。 任何劫持从for控制的语句将导致else块被绕过。


当在一个iterablesearch一个项目时,find一个常见的用例,当find该项目或者通过下面的else块提出/打印"not found"标志时,search被取消:

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

continue不劫持控制,所以控制会在耗尽之后进行到else控制。

什么时候执行一个else ? 当它的条件是错误的。 这个whileelse while完全else 。 所以你可以把while / else看作只是一个if一直运行它的真实条件,直到它评估错误。 break不会改变这一点。 它只是跳跃的包含循环没有评估。 else只在评估 if / while条件为false时执行。

for是相似的,除了它的假状态耗尽了它的迭代器。

continuebreak不要执行else 。 那不是他们的function。 break退出包含循环。 continue返回到包含循环的顶部,在循环条件被评估。 这是评估if为虚假(或没有更多的项目)执行其他方式,而不是其他方式。

这就是它的实质意义:

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

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

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

如果returnelse子句将不会被执行,因为return离开函数。 你可能会想到的唯一的例外就是,它的目的是确保它总是被执行。

continue跟这个事情没有什么特别的关系。 它导致循环的当前迭代结束,这可能会结束整个循环,显然在这种情况下,循环没有被break结束。

try/else类似:

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

如果你认为你的循环是一个类似这样的结构(有点伪代码):

 loop: if condition then ... //execute body goto loop else ... 

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

所以请注意, 只有在条件被检查时才会执行 else 。 这意味着如果你在执行过程中退出循环的主体,例如returnbreak ,由于条件不再被检查, else不会执行else情况。

另一方面continue停止当前的执行,然后跳回来检查循环的条件,这就是为什么在这种情况下可以达到else

当我看着Raymond Hettinger的一个演讲时,我在这个循环的else章节里得到了一个小小的回忆 ,他讲了一个他认为应该叫做nobreak的故事。 看看下面的代码,你认为它会做什么?

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

你猜怎么着? 那么,说nobreak的部分只会被执行,如果一个break语句没有被打在循环中。

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

 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循环中的else语句与elif链中的else语句完全相同,只有在它之前的条件都不为True时才会执行。 (或者用return或者exception来中断执行)如果我的循环不适合这个规范,通常我会select不使用for: else ,因为你发布这个问题的确切原因是:它是非直观的。

其他人已经解释了while/for...else ,而Python 3语言参考具有权威性的定义(请参阅while和for ),但这里是我个人的助记符,FWIW。 我想我的关键是把它分解成两部分:一部分是为了理解与条件循环有关的else的含义,一部分是为了理解循环控制。

我发现最简单的方法是先从理解开始, while...else

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

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

for每一个项目,做的东西,但如果你用完了,这样做

在这两种情况下,只有在没有更多项目要处理时才能到达else部分,并且最后一个项目已经以规则的方式处理(即不breakreturn )。 continue只是回头看看是否有更多的项目。 我对这些规则的记忆适用于for

breakreturn ,没有else事可做,
当我说continue ,这是“循环回来开始”给你

– “循环回到开始”的意思,显然是循环的开始,我们检查迭代中是否还有更多的项目,所以就else而言, continue真的起不到任何作用。

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

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

 if cond: # 1 else: # 2 

概括为:

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

很好。

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


这是一个来自8thlight博客的例子:

在8thlight博客的链接文章中,Word Wrap kata被认为是:将换行符添加到string(下面的代码片段中的svariables)以使它们适合给定的宽度(下面的片段中的lengthvariables)。 在某一点上,实现看起来如下(Java):

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

而下一个testing,目前失败的是:

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

所以我们有条件工作的代码:当满足特定条件时,添加换行符。 我们希望改进处理多行换行的代码。 本文提出的解决scheme提出应用(if-> while)转换,然而作者发表评论:

while循环不能有else子句,所以我们需要在elsepath中less做elsepath。 再次,这是一个重构。

这迫使在一个失败的testing环境中对代码做更多的修改:

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

在TDD中,我们希望编写尽可能less的代码来使testing通过。 感谢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 

我看到它的方式, else:当你迭代循环结束时触发。

如果你breakreturnraise你不迭代循环结束,你停止immeadiately,因此else:块将不会运行。 如果continue ,仍然迭代循环结束,因为继续跳到下一个迭代。 它不会停止循环。

else子句看作是循环结构的一部分; break完全打破了循环结构,从而跳过了else子句。

但是实际上,我的思维映射只不过是C / C ++模式的“结构化”版本:

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

所以当我遇到for...else或者自己写这些for...else ,而不是直接理解它的时候,我把它理解成上面对模式的理解,然后计算出python语法映射到模式的哪些部分。

(我把“结构化”放在恐吓报价中,因为不同之处不在于代码是结构化的还是非结构化的,而仅仅是关键字和语法是否专用于特定的结构)

我想到的方式,关键是考虑continue而不是else的意义。

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

那么你只需要记住在正常循环终止之后执行else子句。