【问题标题】:How can I make sense of the `else` clause of Python loops?如何理解 Python 循环的“else”子句?
【发布时间】:2016-10-05 04:25:30
【问题描述】:

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

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

else 子句的主体是执行某些清理操作的好地方,并且在循环正常终止时执行:即,使用returnbreak 退出循环会跳过@ 987654330@ 条款; continue 执行后退出。我知道这只是因为我只是looked it up(又一次),因为我永远记不起何时执行else 子句。

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

我将我持续存在的不确定性归咎于关键字的选择:我发现 else 对于这种语义非常难以记忆。我的问题不是“为什么将此关键字用于此目的”(我可能会投票关闭,尽管只有在阅读了答案和 cmets 之后),而是 我怎么能想到 else 关键字,以便它语义是有道理的,因此我能记住它?

我确信对此进行了相当多的讨论,并且我可以想象做出选择是为了与 try 语句的 else: 子句(我也必须查找)保持一致,并且不添加到 Python 保留字列表的目标。也许选择else的原因会澄清它的功能,让它更容易记住,但我是把名字和功能联系起来,而不是历史解释本身。

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

【问题讨论】:

  • @alexis Dharman 的编辑是为了让您的问题不是基于意见的,同时保持问题本身,IMO 编辑使帖子变得更好(并且不值得关闭)
  • @Dharman,我感谢您的努力,但您的编辑完全扭曲了问题的意图和内容。请停下来。
  • “我怎样才能想到 else 关键字,以便它的语义有意义,因此我可以记住它?” - 具体向您解释我们可以如何提供帮助你个人记得else 的工作原理不是一个有用的问题,Dharman 是我撤回对该问题的密切投票的唯一原因,因为没有它,问题是基于意见的
  • 谢谢你的解释,@nick。然而,达曼的标题使它成为一个非常不同的问题的重复。如果你确信这太基于意见,我可以带着你的投票离开。但请不要理会这个问题。
  • 另外,问题是关于这个设计的意义,而不是它的作用。

标签: python loops for-loop while-loop


【解决方案1】:

带有else 子句的while statement

while condition:
    suite
else:
    exhausted

完全等价于

while True:
    if not condition:
        exhausted
        break
    suite

带有else 子句的for statement

for item in iterable:
    suite
else:
    exhausted

完全等价于

iterator = iter(iterable)
try:
    while True:
        item = next(iterator)
        suite
except StopIteration:
    exhausted

它有助于理解套件语句中breakcontinue 语句的效果。

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

要记住else 子句的含义,可以将循环语句解释为

if loop_breaks:
    pass
else:
    exhausted

【讨论】:

    【解决方案2】:

    if 语句在其条件评估为假时运行其else 子句。 同样,while 循环在其条件评估为 false 时运行 else 子句。

    此规则符合您描述的行为:

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

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

    【讨论】:

    • 这是一个非常好的答案。将循环视为一系列 elif 语句,else 行为将暴露其自然逻辑。
    • 我也喜欢这个答案,但它并没有与一系列elif 语句进行类比。有an answer,它有一个净赞成票。
    • 不完全是,while 循环可能在 breaks 之前满足 False 条件,在这种情况下 else 不会运行但条件为 False。与for 循环类似,它可以在最后一个元素上使用break
    【解决方案3】:

    如果您将elsefor 配对,可能会造成混淆。我不认为关键字else 是这种语法的最佳选择,但是如果您将else 与包含breakif 配对,您会发现它实际上是有意义的。如果前面没有 if 语句,else 几乎没有用处,我相信这就是语法设计者选择关键字的原因。

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

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

    【讨论】:

      【解决方案4】:

      if 何时执行else?当它的条件为假时。 while/else 的情况完全相同。所以你可以把while/else 看作是一个if,它一直运行它的真实条件,直到它评估为假。 break 不会改变这一点。它只是跳出包含循环而没有评估。 else 仅在评估if/while 条件为假时执行。

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

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

      【讨论】:

      • 你说的听起来很明智,但是将三个终止条件混为一谈,“直到 [条件] 为 False 或中断/继续”,是错误的:至关重要的是,else 子句在以下情况下执行使用continue(或正常情况下)退出循环,但如果我们使用break 退出,不会。这些细微之处是我试图真正了解else 捕获什么以及它没有捕获什么的原因。
      • @alexis 是的,我需要在那里澄清一下。已编辑。 continue 不执行 else,但确实返回到循环的顶部,然后可能评估为 false。
      【解决方案5】:
      # 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']
      '''
      

      【讨论】:

        【解决方案6】:

        最好这样想: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

        【讨论】:

        • 听起来很不错...但是当事情顺利时,您会期望执行else 子句,不是吗?我又开始糊涂了……
        • 我不得不在“技术上,它不是 [语义上与其他所有 else] 相似”,因为 else 在 for 循环中没有任何条件时运行评估为 True,正如我在 my answer 中演示的那样
        • @TadhgMcDonald-Jensen 您也可以在False 上打破循环。因此,for 如何损坏的问题取决于用例。
        • 没错,我正在寻求一种方法来以某种方式将发生的事情与“else”的英文含义联系起来(这确实反映在python中else的其他用法中)。您提供了一个很好的直观总结 else 所做的事情,@Moses,但没有说明我们如何将此行为与“其他”相关联。如果使用不同的关键字(例如,this answer 中提到的相关问题中的nobreak),则更容易理解。
        • 这真的与“事情进展顺利”无关。当 if/while 条件评估为 false 或 for 没有项目时,else 纯粹执行。 break 存在包含循环(在else 之后)。 continue 返回并再次评估循环条件。
        【解决方案7】:

        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:向字符串添加换行符(下面 sn-ps 中的 s 变量)以使其适合给定宽度(length 中的变量下面的sn-ps)。在某一时刻,实现如下所示(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
        

        【讨论】:

          【解决方案8】:

          通常我倾向于考虑这样的循环结构:

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

          【讨论】:

          • 对。但是一个循环运行了多次,所以有点不清楚你是如何将它应用到 f​​or 循环中的。你能澄清一下吗?
          • @alexis 我已经重做了我的答案,我认为现在更清楚了。
          【解决方案9】:

          我的思路,关键是要考虑continue的含义,而不是else

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

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

          【讨论】:

            【解决方案10】:

            如果您将循环视为类似于此的结构(有点伪代码):

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

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

            因此请注意else 只有在检查条件时才会执行。这意味着如果您在执行过程中退出循环体,例如使用returnbreak,由于不再检查条件,因此不会执行else 案例。

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

            【讨论】:

            • 我非常喜欢这个答案,但您可以简化:省略end 标签,只需将goto loop 放在if 正文中。甚至可能将if 放在与标签相同的行上,这样看起来就很像原版了。
            • @Bergi 是的,我认为这样会更清楚一些,谢谢。
            【解决方案11】:

            在我看来,else: 在您迭代循环结束时触发。

            如果您 breakreturnraise 不迭代循环结束,您会立即停止,因此 else: 块将不会运行。如果您 continue 您仍然迭代循环结束,因为 continue 只是跳到下一次迭代。它不会停止循环。

            【讨论】:

            • 我喜欢这样,我认为您正在做某事。它与循环关键字之前糟糕的旧时代的循环实现方式有一点关系。 (即:检查被放置在循环的 底部,成功时将 goto 置于顶部。)但它是投票率最高的答案的较短版本......
            • @alexis,主观的,但我发现我的表达方式更容易思考。
            • 其实我同意。如果只是因为它更简洁。
            【解决方案12】:

            这就是它的本质含义:

            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:
                ...
            

            【讨论】:

              【解决方案13】:

              循环的else 子句让我陷入困境的时刻是在我观看Raymond Hettinger 的演讲时,他讲述了一个关于他认为应该如何将其命名为nobreak 的故事。看看下面的代码,你认为它会做什么?

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

              你猜它有什么作用?好吧,只有当 break 语句没有在循环中被命中时,才会执行显示 nobreak 的部分。

              【讨论】:

                【解决方案14】:

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

                我发现从了解while...else 开始最简单:

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

                for...else的助记词基本一致:

                for 每个项目,做点什么,但else 如果你用完了,就做这个

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

                breaking 或returning 时,else 无事可做,
                当我说continue 时,这对你来说就是“循环回到开始”

                – 带有“loop back to start”的意思,显然,循环的开始,我们检查iterable中是否还有其他项目,所以就else而言,continue真的没有完全是角色。

                【讨论】:

                • 我建议可以通过说 for/else 循环的通常目的是检查项目来增强它停止,否则你的物品用完了。 “else”的存在是为了处理“你用完了物品(没有找到你要找的东西)”部分。
                • @supercat:可能是,但我不知道最常见的用途是什么。当您完成所有项目时,else 也可以用来做某事。示例包括编写日志条目、更新用户界面或通知您已完成的其他一些过程。什么都行,真的。此外,某些代码的“成功”案例在循环内以break 结尾,而else 用于处理在迭代期间未找到任何合适项目的“错误”案例(也许这就是你的想法?)。
                • 我想到的案例恰好是成功案例以“break”结尾,而“else”处理缺乏成功的案例。如果循环中没有“中断”,则“else”代码也可以简单地跟随循环作为封闭块的一部分。
                • 除非您需要区分循环遍历所有可迭代项而不中断的情况(这是一个成功的情况)和没有中断的情况。然后你必须将“终结”代码放在循环的else 块中,或者使用其他方式跟踪结果。我基本上同意,我只是说我不知道​​人们如何使用此功能,因此我想避免假设“else 处理成功案例”或“else 处理不成功案例” ”的情景更为常见。但你有一个好点,所以评论upvoted!
                【解决方案15】:

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

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

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

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

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

                【讨论】:

                • else 在哪里?如果你的意思是 done: 标签代表代理或 else:,我相信你完全倒退了。
                • @alexis 'else' 代码将在done: 标签之前 填写“...”。总体对应关系最好这样说:Python 具有else-on-loop 结构,因此您可以在没有goto 的情况下表达这种控制流模式。
                • 还有其他方法可以执行此控制流模式,例如通过设置一个标志。这就是else 避免的。
                猜你喜欢
                • 1970-01-01
                • 2022-12-10
                • 2022-09-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-09-15
                • 1970-01-01
                • 2023-03-27
                相关资源
                最近更新 更多