我如何理解Python循环的`else`子句?
许多Python程序员可能不知道while
循环和for
循环的语法包含一个可选的else:
语句:
for val in iterable: do_something(val) else: clean_up()
else
子句的主体对于某些types的清理操作是一个好的地方,并且在循环的正常结束时执行:即,用return
或break
退出循环跳过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
块被绕过。
当在一个iterable
search一个项目时,find一个常见的用例,当find该项目或者通过下面的else
块提出/打印"not found"
标志时,search被取消:
for items in basket: if isinstance(item, Egg): break else: print("No eggs in basket")
continue
不劫持控制,所以控制会在耗尽之后进行到else
控制。
什么时候执行一个else
? 当它的条件是错误的。 这个while
和else
while
完全else
。 所以你可以把while
/ else
看作只是一个if
一直运行它的真实条件,直到它评估错误。 break
不会改变这一点。 它只是跳跃的包含循环没有评估。 else
只在评估 if
/ while
条件为false时执行。
for
是相似的,除了它的假状态耗尽了它的迭代器。
continue
和break
不要执行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: ...
如果return
, else
子句将不会被执行,因为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
。 这意味着如果你在执行过程中退出循环的主体,例如return
或break
,由于条件不再被检查, 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
部分,并且最后一个项目已经以规则的方式处理(即不break
或return
)。 continue
只是回头看看是否有更多的项目。 我对这些规则的记忆适用于for
:
当
break
或return
,没有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(下面的代码片段中的s
variables)以使它们适合给定的宽度(下面的片段中的length
variables)。 在某一点上,实现看起来如下(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
子句,所以我们需要在else
path中less做else
path。 再次,这是一个重构。
这迫使在一个失败的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:
当你迭代循环结束时触发。
如果你break
或return
或raise
你不迭代循环结束,你停止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
子句。