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

时间:2016-06-05 13:41:04

标签: python loops for-loop while-loop

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

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

else子句的主体是某些清理操作的好地方,并且在循环的正常终止时执行:即,使用return或{{退出循环1}}跳过break子句; else执行后退出。我之所以知道这只是因为我只是looked it up(又一次),因为当执行continue子句时我永远无法记住

始终?在"失败"循环,顾名思义?定期终止?即使循环退出else?如果不抬头,我永远无法完全确定。

我责怪我对关键字选择的持续存在的不确定性:我发现return对于这种语义非常不符合要求。我的问题不是"为什么这个关键字用于此目的" (虽然只是在阅读了答案和评论之后,我可能会投票关闭,但是如何考虑else关键字以使其语义有意义,因此我能记住它吗? /强>

我确信对此有相当多的讨论,我可以想象选择是为了与else语句的try条款(其中的一致性)保持一致我还必须查找),目的是不添加到Python的保留字列表中。也许选择else:的原因将澄清其功能并使其更令人难忘,但我将名称与功能联系起来,而不是在历史解释本身之后。

this question的答案,其中我的问题作为副本暂时关闭,包含了许多有趣的背景故事。我的问题有一个不同的焦点(如何将else的特定语义与关键字选择联系起来),但我觉得应该在某个地方找到这个问题的链接。

14 个答案:

答案 0 :(得分:211)

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

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

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

  • 在正常执行中,while循环重复运行,直到条件计算为false,因此自然退出循环会运行else子句。
  • 当您执行break语句时,退出循环而不评估条件,因此条件不能计算为false,并且您永远不会运行else子句。
  • 当您执行continue语句时,您再次评估条件,并完成您在循环迭代开始时的正常操作。 因此,如果条件为真,则保持循环,但如果为假,则运行else子句。
  • 退出循环的其他方法(例如return)不会评估条件,因此不会运行else子句。

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

答案 1 :(得分: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")  

continue并未劫持for的控制权,因此在else用尽后,控件将继续for

答案 2 :(得分:30)

if何时执行else?当它的条件是假的。 while / else完全相同。因此,您可以将while / else视为一个if,它一直运行其真实条件,直到它评估为false。 break不会改变它。它只是包含循环的跳转而没有评估。仅当评估 else / if条件为false时才会执行while

for是类似的,除了它的错误条件是耗尽它的迭代器。

continuebreak不执行else。这不是他们的功能。 break退出包含循环。 continue返回到包含循环的顶部,其中评估循环条件。这是将if / while评估为false(或for没有更多项目)执行else而无其他方式的行为。

答案 3 :(得分: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: ... 类似:

 <my-directive  apipoint="customerApi" modeldisplay="customer.selected"  
    ng-model="customer.selected" searchresults="customeruserprofile" change="customerChanged"></my-directive>

答案 4 :(得分:20)

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

loop:
if condition then

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

它可能会更有意义。循环基本上只是一个if语句,在条件为false之前重复。这是重点。循环检查它的条件并看到它是false,从而执行else(就像普通的if/else一样)然后完成循环。

请注意,else 仅在检查条件时执行。这意味着如果您在执行过程中退出循环体,例如returnbreak,因为不再检查条件,else案例赢了“执行。

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

答案 5 :(得分:14)

我对循环的else条款的关注时刻是我Raymond Hettinger观看演讲的时候,他讲了一个关于他应该怎么称呼nobreak的故事。 }。看看下面的代码,您认为它会做什么?

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

你猜它会怎样?好吧,只有在循环中没有点击nobreak语句时才会执行break部分。

答案 6 :(得分:7)

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

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语句与else链上的elif语句完全相同,只有在它之前没有任何条件求值时才会执行真正。 (或使用return或异常中断执行)如果我的循环不符合此规范,我通常会选择不使用for: else,因为您发布此问题的确切原因:它不直观。

答案 7 :(得分:6)

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

通过理解while...else

,我觉得最容易开始
  

while你有更多项目,做事,else如果你用完了,就这样做

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

  

for每个项目,做一些事情,但如果你用完了else,请执行此操作

在这两种情况下,else部分只有在没有其他项目需要处理时才会到达,并且最后一项已经定期处理(即没有break或{{1} })。 return只会返回并查看是否还有其他项目。我对这些规则的助记符适用于continuewhile

  

forbreak时,return无法执行任何操作   当我说else时,那个&#34;&#34;循环回到开始&#34;为你

- 用&#34;循环回到开始&#34;显然,这意味着循环的开始,我们检查迭代中是否还有其他项,因此就continue而言,else实际上根本没有任何作用。

答案 8 :(得分:6)

Test-driven development(TDD)中,当使用Transformation Priority Premise范例时,将循环视为条件语句的泛化。

如果您只考虑简单的if/else(无elif)语句,则此方法与此语法完美结合:

if cond:
    # 1
else:
    # 2

概括为:

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

很好。

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

以下是8thlight blog的示例:

在8thlight博客的链接文章中,我们考虑使用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-&gt; while)转换,但作者发表评论:

  

虽然循环不能包含else子句,但我们需要通过在else路径中减少if路径来消除String result = ""; while (s.length() > length) { result += s.substring(0, length) + "\n"; s = s.substring(length); } result += s; 路径。同样,这是重构。

强制在一次失败测试的上下文中对代码进行更多更改:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

在TDD中,我们希望尽可能少地编写代码以使测试通过。感谢Python的语法,可以进行以下转换:

从:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s

为:

  log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
 log4j:WARN Please initialize the log4j system properly.
 Exception in thread "main" org.hibernate.HibernateException: Could not    parse configuration: /hibernate.cfg.xml
at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:1491)
at org.hibernate.cfg.Configuration.configure(Configuration.java:1425)
at org.hibernate.cfg.Configuration.configure(Configuration.java:1411)
at SimpleTest.main(SimpleTest.java:11)
Caused by: org.dom4j.DocumentException: Connection timed out: connect Nested exception: Connection timed out: connect
at org.dom4j.io.SAXReader.read(SAXReader.java:484)
at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:1481)
... 3 more

答案 9 :(得分:5)

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

如果您breakreturnraise没有超过循环结束,则会立即停止,因此else:块赢了&# 39;跑。如果你continue仍然迭代循环结束,那么继续只是跳到下一次迭代。它不会停止循环。

答案 10 :(得分:3)

else子句视为循环结构的一部分; break完全打破了循环结构,因此跳过else子句。

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

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

所以,当我遇到for...else或自己编写时,而不是直接理解 ,我会在心理上将其转化为对模式的上述理解,然后找出python的哪些部分语法映射到模式的哪些部分。

(我把'结构化'放在恐慌引号中,因为差异不在于代码是结构化的还是非结构化的,而在于是否存在专用于特定结构的关键字和语法)

答案 11 :(得分:0)

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

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

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

答案 12 :(得分:0)

如果您尝试将elsefor配对,可能会令人困惑。我不认为关键字else是此语法的绝佳选择,但如果您将elsebreak配对,则可以看出它确实有意义。

让我用人类语言证明它。

  

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


如果else循环中没有break,则for几乎无用。

答案 13 :(得分: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']
'''