【问题标题】:Why does python use 'else' after for and while loops?为什么python在for和while循环之后使用'else'?
【发布时间】:2012-04-16 07:44:15
【问题描述】:

我了解这个结构的工作原理:

for i in range(10):
    print(i)

    if i == 9:
        print("Too big - I'm giving up!")
        break
else:
    print("Completed successfully")

但我不明白为什么在这里使用else 作为关键字,因为它表明有问题的代码仅在for 块未完成时运行,这与它的作用相反!无论我怎么想,我的大脑都无法从for 语句无缝推进到else 块。对我来说,continuecontinuewith 会更有意义(我正在努力训练自己阅读它)。

我想知道 Python 编码员是如何在他们的脑海中读懂这个结构的(或者,如果你愿意,可以大声读出来)。也许我遗漏了一些可以让这些代码块更容易被破译的东西?

【问题讨论】:

  • 你可能想把它翻译成你脑海中的“then”。
  • 不要忘记 Python 之禅中的关键台词:“……除非你是荷兰人,否则这种方式一开始可能并不明显。”
  • 在我的脑海中,我把它翻译成“if not break”。而且,由于break“I've found it”循环中被大量使用,你可以将其翻译成“if not found”,这距离不远else 读取的内容
  • 我认为很多人的真正问题是“for ... else foo() 和将foo() 放在 for 循环之后有什么区别?”答案是只有如果循环包含break(如下文详细描述),它们的行为会有所不同。

标签: python if-statement for-loop for-else


【解决方案1】:

一个常见的结构是运行一个循环,直到找到一些东西,然后退出循环。问题是,如果我跳出循环或循环结束,我需要确定发生了哪种情况。一种方法是创建一个标志或存储变量,让我进行第二次测试以查看循环是如何退出的。

例如假设我需要搜索一个列表并处理每个项目,直到找到一个标志项目,然后停止处理。如果缺少标志项,则需要引发异常。

使用您拥有的 Python for...else 构造

for i in mylist:
    if i == theflag:
        break
    process(i)
else:
    raise ValueError("List argument missing terminal flag.")

将此与不使用此语法糖的方法进行比较:

flagfound = False
for i in mylist:
    if i == theflag:
        flagfound = True
        break
    process(i)

if not flagfound:
    raise ValueError("List argument missing terminal flag.")

在第一种情况下,raise 紧密绑定到它使用的 for 循环。在第二种情况下,绑定没有那么强,并且在维护期间可能会引入错误。

【讨论】:

  • 这比选择的答案更好地解释了作者并没有真正理解 for-else 的含义!
  • 我不得不说这种语法糖可能会腐蚀你的项目的牙齿。这不会成为Python: the good parts 的书。
  • 您能否确认在您的示例中,mylist 中的每个项目都发生mylist 严格在theflag 之前,而不是theflag 本身?这是预期的吗?
  • process 将在到达theflag 之前存在于列表中的每个i 上执行,它不会在theflag 之后的列表中的元素上执行,并且不会在theflag上执行。
  • 如果可迭代对象没有元素,else 语句也会被执行
【解决方案2】:

即使对于经验丰富的 Python 编码人员来说,这也是一个奇怪的结构。当与 for 循环结合使用时,它的基本意思是“在可迭代中找到一些项目,否则如果没有找到,请执行...”。如:

found_obj = None
for obj in objects:
    if obj.key == search_key:
        found_obj = obj
        break
else:
    print('No object found.')

但是任何时候你看到这个结构,一个更好的选择是将搜索封装在一个函数中:

def find_obj(search_key):
    for obj in objects:
        if obj.key == search_key:
            return obj

或者使用列表推导:

matching_objs = [o for o in objects if o.key == search_key]
if matching_objs:
    print('Found {}'.format(matching_objs[0]))
else:
    print('No object found.')

它在语义上不等同于其他两个版本,但在非性能关键代码中运行良好,无论您是否迭代整个列表都无关紧要。其他人可能不同意,但我个人会避免在生产代码中使用 for-else 或 while-else 块。

另见[Python-ideas] Summary of for...else threads

【讨论】:

  • 列表理解是错误的单行。如果您正在寻找单个项目,例如 for 循环示例,并且想要使用生成器表达式/列表理解,那么您需要 next((o for o in objects if o.key == search_key), None) 或将其包装在 try / except 中并使用没有默认值,而不是 if / else
  • 和 Lance Helsten 的回答一样,在实际情况下最好使用 for/else 构造。
  • 干杯。我有一个严重缩进的文件,其中 elsefor 配对,我不知道这是合法的。
  • 值得一提的是,即使 for 循环有值,else 子句也会运行,除非像本例中那样显式运行 break 语句。从上面的文档中:“else 子句还有另一个问题:如果循环中没有break,则else 子句在功能上是多余的。”。例如for x in [1, 2, 3]:\n print x\n else:\n print 'this executes due to no break'
  • ""在可迭代项中找到一些项目,否则如果没有找到,请执行..." 这是错误的。我们进行迭代的原因有很多,而不是“找到东西”。
【解决方案3】:

Raymond Hettinger 有一个出色的演示文稿,标题为 Transforming Code into Beautiful, Idiomatic Python,其中他简要介绍了 for ... else 构造的历史。相关部分是“区分循环中的多个出口点”starting at 15:50 并持续大约三分钟。以下是重点:

  • for ... else 构造由 Donald Knuth 设计,用于替代某些 GOTO 用例;
  • 重用 else 关键字是有道理的,因为“这是 Knuth 使用的,而且人们知道,当时所有 [for 语句] 都在下面嵌入了 ifGOTO,他们期望 @ 987654333@;"
  • 事后看来,它应该被称为“no break”(或者可能是“nobreak”),这样就不会混淆了。*

所以,如果问题是,“他们为什么不更改这个关键字?”然后Cat Plus Plus probably gave the most accurate answer——在这一点上,它对现有代码的破坏性太大而无法实用。但是,如果您真正要问的问题是为什么首先要重用 else,那么显然这在当时似乎是个好主意。

就个人而言,我喜欢在else 一眼看去可能被误认为属于循环内部的地方评论# no break 的折衷方案。它相当清晰和简洁。这个选项在他的回答末尾的the summary that Bjorn linked 中得到了简短的提及:

为了完整起见,我应该提一下 语法,想要这个语法的程序员现在就可以拥有了:

for item in sequence:
    process(item)
else:  # no break
    suite

* 视频那部分的额外引述:“就像我们调用 lambda makefunction, 没有人会问,'lambda 做什么?'”

【讨论】:

  • 为什么不在 else 旁边添加对 nobreak 的支持,让两者相等且并存,并制定明确的 PEP 样式规则,即应该使用 nobreak 而不是 else?
  • @jaaq 我不能代表 Python 核心开发人员,但考虑一下 PEP 20 行“应该有一种——最好只有一种——明显的方法。”
  • 是的,没错,但他们对除法运算符做了同样的事情,可以从__future__ 导入除法以将 / 替换为标准除法并添加 // 地板除法运算符。
  • recent pep doc 声明多种方式都可以,但不是多种明显方式。由于nobreak 关键字可能明显,也许这确实是一种改进语法的方法。
  • "nobreak" 将是一个新关键字,并且,作为一项规则,语言设计者非常不愿意将关键字添加到现有语言中,因为它会破坏所有使用该标识符的代码否则。
【解决方案4】:

为了简单起见,你可以这样想;

  • 如果在for循环中遇到break命令,将不会调用else部分。
  • 如果在for循环中没有遇到break命令,则会调用else部分。

换句话说,如果 for 循环迭代没有被 break“破坏”,则将调用 else 部分。

【讨论】:

  • 如果循环体引发异常,else 块也不会被执行。
  • 如果列表为空且for循环根本不迭代,else块也会被执行。
【解决方案5】:

因为他们不想在语言中引入新的关键字。每个人都会窃取一个标识符并导致向后兼容性问题,因此它通常是最后的手段。

【讨论】:

  • 在这种情况下,finally 似乎是一个更好的选择。在引入此构造时,finally 关键字是否还没有出现?
  • @Wallacoloo finally 也好不到哪里去,因为它意味着该块将始终在循环之后执行,而事实并非如此(因为仅将代码放到在循环之后运行)。
  • 它也不能是finally,因为在for循环中使用continue时也会执行else子句——这可能是很多次,而不仅仅是在最后。
  • @pepr else 子句执行不受continue 影响(docstest code
  • @AirThomas:+1。你说的对。仅当 continue 是最后一次迭代时才执行 else
【解决方案6】:

我认为文档对else继续

有很好的解释

[...] 当循环因列表耗尽而终止时(使用 for)或条件变为假(使用 while)时执行,但不会在循环被 break 语句终止时执行。”

来源:Python 2 docs: Tutorial on control flow

【讨论】:

    【解决方案7】:

    我发现“获取” for/else 所做的最简单的方法,更重要的是,何时使用它,是专注于 break 语句跳转到的位置。 For/else 结构是单个块。 break 跳出块,因此跳过了 else 子句。如果 else 子句的内容只是简单地跟在 for 子句之后,它永远不会被跳过,因此必须通过将其放在 if 中来提供等效的逻辑。之前已经说过,但不是完全用这些话,所以它可能对其他人有所帮助。尝试运行以下代码片段。为了清楚起见,我全心全意地支持“不中断”的评论。

    for a in range(3):
        print(a)
        if a==4: # change value to force break or not
            break
    else: #no break  +10 for whoever thought of this decoration
        print('for completed OK')
    
    print('statement after for loop')
    

    【讨论】:

    • “break 跳出块,因此跳过了 else 子句” - 虽然这可能有助于“获取”for: /else:,它并没有真正为关键字else 提供理由。鉴于此处给出的框架,then: 似乎更自然。 (有原因选择else,在其他答案中给出 - 他们只是没有在这里提供。)
    【解决方案8】:

    我读到的是这样的:

    如果仍然有条件运行循环,则做一些事情,否则做些别的事情。

    【讨论】:

    • 你的 still on the conditions 是有帮助的 (+1) 虽然它是错误的 - 它是人类的 ;-)
    • -1; for:/else: 的这个发音听起来就像 else: 总是在循环之后运行,但事实并非如此。
    【解决方案9】:

    由于技术部分已经得到了相当多的回答,我的评论只是与产生这个 recycled 关键字的混淆有关。

    作为 Python 一种非常雄辩的编程语言,对关键字的滥用更加臭名昭著。 else 关键字完美地描述了决策树流程的一部分,“如果你不能这样做,(否则)就这样做”。这是在我们自己的语言中暗示的。

    相反,将此关键字与whilefor 语句一起使用会造成混淆。原因是,我们作为程序员的职业告诉我们else 语句位于决策树中;它的逻辑范围,一个有条件地返回要遵循的路径的包装器。同时,循环语句有一个象征性的明确目标来达到某些目标。在过程的连续迭代之后达到目标。

    if / else 指明要遵循的路径。循环沿着一条路径直到“目标”完成

    问题在于else 是一个明确定义条件中最后一个选项的词。单词的语义由 Python 和人类语言共享。但是人类语言中的 else 词从不用于表示某人或某物在完成某事后将采取的行动。如果在完成它的过程中出现问题(更像是 break 语句),将使用它。

    最后,关键字将保留在 Python 中。很明显这是错误的,当每个程序员都试图想出一个故事来理解它的用法时,就像一些助记符一样。如果他们选择了关键字then,我会很高兴的。我相信这个关键字非常适合那个迭代流程,循环之后的payoff

    这类似于某些孩子在组装玩具的每一步之后遇到的情况:那么爸爸呢?

    【讨论】:

    • 我认为这个答案解决了我认为 OP 正在谈论的混淆问题。当附加到 for 动作时,else 关键字的作用与您从 else 的英文含义所期望的完全相反。从理论上讲,for ... else 的工作方式可能有所不同,因为当循环中断时,您最终会进入 else 部分,但问题是要使用它来查找元素 x,并处理 x 为没有找到,你可能不得不在整个 for .. else 构造之后使用一个标志或另一个测试
    【解决方案10】:

    很好的答案是:

    • this 解释历史,和
    • this 给予权利 引用以简化您的翻译/理解。

    我的注释来自 Donald Knuth 曾经说过的(抱歉找不到参考资料),即存在一个结构,其中 while-else 与 if-else 无法区分,即(在 Python 中):

    x = 2
    while x > 3:
        print("foo")
        break
    else:
        print("boo")
    

    具有与以下相同的流程(不包括低级差异):

    x = 2
    if x > 3:
        print("foo")
    else:
        print("boo")
    

    关键是 if-else 可以被认为是 while-else 的语法糖,它在 if 块的末尾隐含了 break。相反的含义,即while 循环是if 的扩展,更常见(它只是重复/循环条件检查),因为if 通常在while 之前教授。然而这不是真的,因为这意味着当条件为假时,while-else 中的else 块将每次执行。

    为了便于理解,请这样想:

    如果没有breakreturn 等,循环仅在条件不再为真时结束,在这种情况下else 块也将执行一次。对于 Python for,您必须考虑 C 风格的 for 循环(带条件)或将它们转换为 while

    另一个说明:

    循环内过早的breakreturn 等使得条件不可能变为假,因为在条件为真时执行跳出循环并且它永远不会再回来检查它。

    【讨论】:

      【解决方案11】:

      我读起来像“当iterable完全用完,并且在完成for后即将执行下一条语句时,将执行else子句。”因此,当迭代被break 中断时,将不会执行。

      【讨论】:

        【解决方案12】:

        我同意,这更像是“elif not [condition(s) raise break]”。

        我知道这是一个旧线程,但我现在正在研究同一个问题,我不确定是否有人以我理解的方式获得了这个问题的答案。

        对我来说,在For... elseWhile... else 语句中“阅读”else 有三种方式,它们都是等效的,分别是:

        1. else==if the loop completes normally (without a break or error)
        2. else==if the loop does not encounter a break
        3. else==else not (condition raising break)(想必有这样的条件,不然就不会循环了)

        所以,本质上,循环中的“else”实际上是一个“elif ...”,其中“...”是 (1) no break,相当于 (2) NOT [condition( s) 提高休息时间]。

        我认为关键是else 没有“中断”就毫无意义,所以for...else 包括:

        for:
            do stuff
            conditional break # implied by else
        else not break:
            do more stuff
        

        因此,for...else 循环的基本元素如下,您可以用更简单的英语阅读它们:

        for:
            do stuff
            condition:
                break
        else: # read as "else not break" or "else not condition"
            do more stuff
        

        正如其他发帖人所说,当您能够找到循环正在寻找的内容时,通常会引发中断,因此else: 变为“如果找不到目标项目该怎么办”。

        示例

        您还可以同时使用异常处理、中断和 for 循环。

        for x in range(0,3):
            print("x: {}".format(x))
            if x == 2:
                try:
                    raise AssertionError("ASSERTION ERROR: x is {}".format(x))
                except:
                    print(AssertionError("ASSERTION ERROR: x is {}".format(x)))
                    break
        else:
            print("X loop complete without error")
        

        结果

        x: 0
        x: 1
        x: 2
        ASSERTION ERROR: x is 2
        ----------
        # loop not completed (hit break), so else didn't run
        

        示例

        一个简单的例子,一个中断被击中。

        for y in range(0,3):
            print("y: {}".format(y))
            if y == 2: # will be executed
                print("BREAK: y is {}\n----------".format(y))
                break
        else: # not executed because break is hit
            print("y_loop completed without break----------\n")
        

        结果

        y: 0
        y: 1
        y: 2
        BREAK: y is 2
        ----------
        # loop not completed (hit break), so else didn't run
        

        示例

        没有中断、没有引发中断的条件并且没有遇到错误的简单示例。

        for z in range(0,3):
             print("z: {}".format(z))
             if z == 4: # will not be executed
                 print("BREAK: z is {}\n".format(y))
                 break
             if z == 4: # will not be executed
                 raise AssertionError("ASSERTION ERROR: x is {}".format(x))
        else:
             print("z_loop complete without break or error\n----------\n")
        

        结果

        z: 0
        z: 1
        z: 2
        z_loop complete without break or error
        ----------
        

        【讨论】:

          【解决方案13】:

          else 关键字在这里可能会造成混淆,正如许多人所指出的,nobreaknotbreak 之类的关键字更合适。

          为了从逻辑上理解for ... else ...,将其与try...except...else进行比较,而不是if...else...,大多数python程序员都熟悉以下代码:

          try:
              do_something()
          except:
              print("Error happened.") # The try block threw an exception
          else:
              print("Everything is find.") # The try block does things just find.
          

          同样,将break 视为一种特殊的Exception

          for x in iterable:
              do_something(x)
          except break:
              pass # Implied by Python's loop semantics
          else:
              print('no break encountered')  # No break statement was encountered
          

          区别在于python暗含except break,不能写出来,所以变成:

          for x in iterable:
              do_something(x)
          else:
              print('no break encountered')  # No break statement was encountered
          

          是的,我知道这种比较可能既困难又令人厌烦,但它确实澄清了困惑。

          【讨论】:

          • 从资源中复制时应该创建一个指向资源的链接:Nick Coghlan's Python Notes
          • @godaygo 感谢您的链接。初学python时阅读并接受了这个概念,写答案时没有记住出处。
          • @cizixs 你“没有记住出处” 只是碰巧包含了与原文相同的整个 cmets 句子?噢噢噢噢。
          • 我是来找这个的,但是..try: stuff(); except: error(); else: ok()try: stuff(); ok(); except: error()真的不一样吗?
          【解决方案14】:

          我想知道 Python 编码员是如何在他们的脑海中读懂这个结构的(或者,如果你愿意,可以大声读出来)。

          我只是在脑海中思考:

          “否则没有遇到break...”

          就是这样!

          这是因为只有在for 循环中没有遇到break 语句时,else 子句才会执行。

          参考:

          请参见此处:https://book.pythontips.com/en/latest/for_-_else.html#else-clause(已添加重点,“not”更改为“NOT”):

          for 循环也有一个我们大多数人都不熟悉的else 子句。 else 子句在循环正常完成后执行。这意味着循环没有遇到 break 语句。


          话虽如此,我建议反对使用这种不寻常的语言特性。不要在 for 循环后使用 else 子句。这会让大多数人感到困惑,只会减慢他们阅读和理解代码的能力。

          【讨论】:

            【解决方案15】:

            else 语句块中的代码将在for 循环未被破坏时执行。

            for x in xrange(1,5):
                if x == 5:
                    print 'find 5'
                    break
            else:
                print 'can not find 5!'
            #can not find 5!
            

            来自docs: break and continue Statements, and else Clauses on Loops

            循环语句可能有一个 else 子句;它在循环因列表用尽而终止时(使用 for)或条件变为假(使用 while)时执行,但不是在循环由 break 语句终止时执行。下面的循环就是一个例子,它搜索素数:

            >>> for n in range(2, 10):
            ...     for x in range(2, n):
            ...         if n % x == 0:
            ...             print(n, 'equals', x, '*', n//x)
            ...             break
            ...     else:
            ...         # loop fell through without finding a factor
            ...         print(n, 'is a prime number')
            ...
            2 is a prime number
            3 is a prime number
            4 equals 2 * 2
            5 is a prime number
            6 equals 2 * 3
            7 is a prime number
            8 equals 2 * 4
            9 equals 3 * 3
            

            (是的,这是正确的代码。仔细看:else 子句属于 for 循环,而不是 if 语句。)

            当与循环一起使用时,else 子句与 try 语句的 else 子句的共同点比 if 语句的更多:try 语句的 else 子句在没有异常发生时运行,循环的 else 子句在没有异常时运行没有中断发生。有关 try 语句和异常的更多信息,请参阅处理异常。

            也从 C 中借用的 continue 语句继续循环的下一次迭代:

            >>> for num in range(2, 10):
            ...     if num % 2 == 0:
            ...         print("Found an even number", num)
            ...         continue
            ...     print("Found a number", num)
            Found an even number 2
            Found a number 3
            Found an even number 4
            Found a number 5
            Found an even number 6
            Found a number 7
            Found an even number 8
            Found a number 9
            

            【讨论】:

            • 这没有增加任何内容,也没有回答问题,这不是如何而是为什么
            【解决方案16】:

            这是我在上面没有看到其他人提到过的一种思考方式:

            首先,请记住 for 循环基本上只是 while 循环周围的语法糖。比如循环

            for item in sequence:
                do_something(item)
            

            可以(大约)重写为

            item = None
            while sequence.hasnext():
                item = sequence.next()
                do_something(item)
            

            其次,请记住,while 循环基本上只是重复的 if 块!您始终可以将 while 循环理解为“如果此条件为真,则执行主体,然后返回并再次检查”。

            所以 while/else 非常有意义:它与 if/else 的结构完全相同,增加了循环直到条件变为 false 的功能,而不是只检查一次条件。

            然后 for/else 也很有意义:因为所有 for 循环都只是 while 循环之上的语法糖,你只需要弄清楚底层 while 循环的隐式条件是什么,然后 else 对应到该条件变为 False 时。

            【讨论】:

              【解决方案17】:
              for i in range(3):
                  print(i)
              
                  if i == 2:
                      print("Too big - I'm giving up!")
                      break;
              else:
                  print("Completed successfully")
              

              这里的“else”非常简单,只是意思

              1, "如果for clause 完成"

              for i in range(3):
                  print(i)
              
                  if i == 2:
                      print("Too big - I'm giving up!")
                      break;
              if "for clause is completed":
                  print("Completed successfully")
              

              写“for 子句完成”之类的长语句很牛逼,所以引入了“else”。

              else 本质上是一个 if。

              2,不过,for clause is not run at all怎么样

              In [331]: for i in range(0):
                   ...:     print(i)
                   ...: 
                   ...:     if i == 9:
                   ...:         print("Too big - I'm giving up!")
                   ...:         break
                   ...: else:
                   ...:     print("Completed successfully")
                   ...:     
              Completed successfully
              

              所以说完全是逻辑组合:

              if "for clause is completed" or "not run at all":
                   do else stuff
              

              或者这样说:

              if "for clause is not partially run":
                  do else stuff
              

              或者这样:

              if "for clause not encounter a break":
                  do else stuff
              

              【讨论】:

              • else 在 SQL 中充当“事务”。
              【解决方案18】:

              这是除搜索之外的另一个惯用用例。假设您想等待条件为真,例如要在远程服务器上打开的端口,以及一些超时。然后你可以像这样使用while...else 构造:

              import socket
              import time
              
              sock = socket.socket()
              timeout = time.time() + 15
              while time.time() < timeout:
                  if sock.connect_ex(('127.0.0.1', 80)) is 0:
                      print('Port is open now!')
                      break
                  print('Still waiting...')
              else:
                  raise TimeoutError()
              

              【讨论】:

                【解决方案19】:

                我只是想自己重新弄明白。我发现以下有帮助!

                • 将 else 视为与循环内的 if 配对(而不是与 for) - 如果满足条件则中断循环,否则执行此操作- 除了它是一个 else 与多个 ifs 配对!
                • 如果对ifs 完全不满意,请执行else
                • 多个ifs 实际上也可以认为是if-elifs!

                【讨论】:

                • 循环中不需要 if,也不需要循环 - 例如,您可以将 else 与 try-except 一起使用
                【解决方案20】:

                你可以这样想, else 与其他内容或其他内容一样,这些内容并未在循环中完成。

                【讨论】:

                  【解决方案21】:
                  for i in range(10):
                      print(i)
                  
                      if i == 9:
                          print("Too big - I'm giving up!")
                          break;
                  else:
                      print("Completed successfully")
                  

                  break 关键字用于结束循环。如果 i = 9 则循环将结束。虽然任何 if 条件都不太令人满意,那么 else 将完成剩下的部分。

                  【讨论】:

                    【解决方案22】:

                    else 子句在循环正常完成后执行。这意味着 :==> 只有当循环没有被break语句终止时才会在for/while之后执行else块

                    for item in lista:
                    if(obj == item ):
                        print("if True then break will run and else not run")
                        break;
                    else:
                    print("in  else => obj not fount ")
                    

                    【讨论】:

                      【解决方案23】:

                      我认为结构为 for (if) A else B,而 for(if)-else 是一个特殊的 if-else大致。了解else可能会有所帮助。

                      A和B最多执行一次,与if-else结构相同。

                      for(if) 可以认为是一个特殊的 if,它会循环尝试满足 if 条件。一旦满足if条件,A和break否则,B.

                      【讨论】:

                        猜你喜欢
                        • 2012-07-19
                        • 2014-06-30
                        • 2021-11-23
                        • 1970-01-01
                        • 2011-07-22
                        • 2017-01-29
                        • 2022-06-28
                        相关资源
                        最近更新 更多