如何理解 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的具体语义和关键字的选择) ,但我觉得应该有一个链接到这个问题的地方。

13951 次浏览

什么时候 if执行 else?当它的条件为假时。这与 while/else完全相同。因此,您可以将 while/else看作是一个 if,它一直运行其真实条件,直到计算结果为 false。break不会改变这一点。它只是跳出包含循环,没有计算。只有当 else1的 if/while条件为 false 时,才执行 else

for类似,只是它的假条件正在耗尽迭代器。

continuebreak不执行 else。那不是他们的功能。break退出包含循环。continue回到包含循环的顶部,在那里计算循环条件。执行 else的是将 if/while评估为 false (或者 for没有更多的条目)的行为,而不是其他方法。

这就是它的本质含义:

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

这是一种更好的方式来描述这种常见的模式:

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

如果有一个 return,那么将不会执行 else子句,因为 return会离开函数。您可能会想到的唯一例外是 finally,其目的是确保它始终得到执行。

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大小写。

另一方面,continue停止当前的执行,然后跳回来再次检查循环的条件,这就是为什么在这个场景中可以到达 else

最好这样想: 如果前一个 for块中的所有内容都变成 ,以至于到达用尽状态,那么 else块将执行 一直都是

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


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

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

continue不会从 for劫持控制,因此在 for耗尽后控制将继续到 else

通常我会想到这样一个循环结构:

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或一个异常)如果我的循环不符合这个规范,通常我选择退出使用 for: else的确切原因你发布了这个问题: 它是非直观的。

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

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

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

for循环也是如此。如果迭代器有更多的元素,只需考虑条件为 true,否则为 false。

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

但实际上,我的心理映射很简单,它是模式 C/C + + 模式的“结构化”版本:

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

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

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

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

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

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

for...else助记符基本上是一样的:

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

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

breaking 或者 returning 的时候,没有 else要做的事情,
当我说 continue时,对于您来说就是“循环返回到开始”

“ loop back to start”的意思是,很明显,循环的开始,我们检查迭代中是否还有更多的条目,所以就 else而言,continue根本不起任何作用。

当我看到 Raymond Hettinger的一个演讲,他讲了一个他认为应该叫 nobreak的故事。看一下下面的代码,您认为它会做什么?

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

你觉得它能做什么?只有在循环中没有命中 break语句时才会执行 nobreak

测试驱动开发(TDD)中,当使用 改造优先处所范例时,您将循环视为条件语句的泛化。

如果只考虑简单的 if/else(没有 elif)语句,这种方法可以很好地结合这种语法:

if cond:
# 1
else:
# 2

概括为:

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

很好。

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


下面是 第八光博客的一个例子:

在8 thlight blog 的链接文章中,Word Wrap 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子句,所以我们需要通过在 if路径中做更少的事情来消除 else路径。同样,这是一个重构。

在一个测试失败的情况下,它迫使对代码进行更多的更改:

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

在我看来,当迭代超过循环的末尾时,else:会触发。

如果您没有迭代超过循环的末尾,那么您将立即停止,因此 else:块将不会运行。如果您使用的是 continue,那么您仍然需要迭代超过循环的结束,因为 Continualjust 会跳过到下一个迭代。它不会停止循环。

我认为,关键是要考虑 continue而不是 else的意义。

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

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

如果将 elsefor配对,可能会造成混淆。我不认为关键字 else是这种语法的一个很好的选择,但是如果你将 else与包含 breakif配对,你可以看到它实际上是有意义的。如果没有前面的 if语句,那么 else几乎没有用处,我相信这就是语法设计人员选择关键字的原因。

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

每个人在一组嫌疑犯中 if任何人都是罪犯 break的调查。 else报告失败。

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

带有 else子句的 while语句

while condition:
iteration
else:
conclusion

完全等同于

while True:
if not condition:
conclusion
break
iteration

带有 else子句的 for语句

for item in iterable:
iteration
else:
conclusion

完全等同于

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

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

注意。对于没有 else子句的 whilefor语句,用等效代码中的 pass语句替换结论语句。