ChatGPT解决这个技术问题 Extra ChatGPT

为什么python在for和while循环之后使用'else'?

我了解此构造的工作原理:

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 之禅中的关键线:“……除非你是荷兰人,否则这种方式一开始可能并不明显。”
在我的脑海中,我将其翻译成“如果不中断”。而且,由于 break“我找到了” 循环中被大量使用,您可以将其翻译为 “如果未找到”,这与else 读什么
我认为很多人在这里遇到的真正问题是“for ... else foo() 和将 foo() 放在 for 循环之后有什么区别?”答案是,在循环包含 break 时它们的行为不同(如下文详细描述)。
在提出这个问题将近 10 年后,在使用 Python 编程 3 年后,这是我第一次看到“for-else”、“while-else”结构。我希望它的低频使用会进一步减少到蒸发。世界上到处都是初级程序员,他们会“取消缩进”“else”以“修复”他们发现的错误。

M
Mateen Ulhaq

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

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

使用您拥有的 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 循环紧密绑定。在第二种情况下,绑定没有那么强,并且在维护期间可能会引入错误。


我不得不说这种语法糖可能会腐蚀你的项目。这不会成为一本 Python: the good parts 书。
您能否确认在您的示例中,process(i) 发生在 mylist 中的每个项目都严格在 theflag 之前,而不是 theflag 本身?这是预期的吗?
process 将在到达 theflag 之前对列表中存在的每个 i 执行,不会对 theflag 之后的列表中的元素执行,也不会在 theflag 上执行。
如果可迭代对象没有元素,else 语句也会被执行
至于维护,将“for-else”放入“if”块中,看看您的维护人员是否不止一次地超出“else”以绑定到“if”,认为他们正在修复错误。
p
phoenix

即使对于经验丰富的 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'
“”在可迭代项中找到一些项目,否则如果没有找到,则做......”这是错误的。我们迭代的原因有很多,而不是“找到东西”。
C
Community

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

for ... else 构造是由 Donald Knuth 设计的,用于替代某些 GOTO 用例;

重用 else 关键字是有道理的,因为“这是 Knuth 使用的,人们知道,当时所有 [for 语句] 都在下面嵌入了 if 和 GOTO,他们期待 else;”

事后看来,它应该被称为“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”将是一个新的关键字,并且,作为一项规则,语言设计者非常不愿意将关键字添加到现有语言中,因为它会立即破坏所有使用该标识符的代码。
A
Ad Infinitum

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

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

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

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


如果循环体引发异常,也不会执行 else 块。
如果列表为空并且 for 循环根本不迭代,则 else 块也将被执行。
如果喜欢将 for/else 读作 for/nobreak
C
Cat Plus Plus

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


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

我认为文档对 else 有很好的解释,继续

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

来源:Python 2 docs: Tutorial on control flow


N
Neil_UK

我发现“获取” 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')

编辑 - 我注意到这个问题仍在运行

第二个更好的想法...

“不中断”评论是否定的。理解肯定断言要容易得多,那就是 for 迭代已用尽。

for a in range(3):
    print(a)
    if a==4: # change value to force break or not
        print('ending for loop with a break')
        break
else: # for iterable exhausted  
    print('ending for loop as iterable exhausted')

print('for loop ended one way or another')

这也强化了这种解释

if iterable_supplies_a_value:
    run_the_for_with_that_value
else:
    do_something_else

“break 跳出块,因此跳过了 else 子句” - 虽然这可能有助于“获取”for:/else:,但它没有' t 确实为关键字 else 提供了理由。鉴于此处给出的框架,then: 似乎更自然。 (在其他答案中给出了选择 else个原因 - 它们只是没有在此处提供。)
p
pcalcao

我读到它是这样的:

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


你仍然在条件上是有帮助的(+1)虽然它是错误的 - 它是人类;-)
-1; for:/else: 的这种发音听起来好像 else: 将始终在循环之后运行,但事实并非如此。
3
3rdWorldCitizen

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

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

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

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

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

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

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


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

很好的答案是:

这解释了历史,并且

这提供了正确的引用来简化您的翻译/理解。

我在这里的注释来自 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 块。

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

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

另一个注意事项:

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


G
Gabriel Staples

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

我只是在脑海里想:

“否则没有遇到中断……”

而已!

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

参考:

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

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

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


0
0xc0de

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


N
NotAnAmbiTurner

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

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

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

else == 如果循环正常完成(没有中断或错误) else == 如果循环没有遇到中断 else == else not(条件引发中断)(大概有这样的条件,或者你不会有一个循环)

所以,本质上,循环中的“else”实际上是一个“elif ...”,其中“...”是(1)没有中断,相当于(2)NOT [condition(s) raise break]。

我认为关键是 else 没有“break”是毫无意义的,所以 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
----------

c
cizixs

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您“没有记住来源”,而是碰巧包含了与原始评论相同的整句评论?噢噢噢噢。
我来这里是为了找这个,但是.. try: stuff(); except: error(); else: ok()try: stuff(); ok(); except: error() 真的不一样吗?
C
Community

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: ... # 循环失败而没有找到因子 ... print(n, '是质数') ... 2 是质数 3 是质数 4 等于 2 * 2 5 是质数 6 等于 2 * 3 7 是质数 8 等于 2 * 4 9 等于 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) 找到一个偶数 2 找到一个数字 3 找到一个偶数 4 找到一个数字 5 找到一个偶数 6 找到一个数字 7找到一个偶数 8 找到一个数字 9


这没有增加任何内容,也没有回答问题,这不是如何而是为什么。
A
Aaron Gable

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

首先,请记住 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。


A
AbstProcDo
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 中充当“事务”。
J
Jonathan Sudiaman

这是除了搜索之外的另一个惯用用例。假设您想等待某个条件为真,例如在远程服务器上打开一个端口,以及一些超时。然后您可以像这样使用 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()

G
Germaine Goh

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

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


循环中不需要 if,也不需要循环 - 例如,您可以将 else 与 try-except 一起使用
j
jamylak

您可以将它想象为 else 与其他内容或其他内容一样,这些内容不是在循环中完成的。


D
Dilux
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 将完成剩下的部分。


M
Mhmoud Sabry

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 ")

J
Jie Zhang

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

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

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