【问题标题】:Python: Else statement with 2 for loopsPython:带有 2 个 for 循环的 Else 语句
【发布时间】:2018-09-14 06:33:40
【问题描述】:

我正在寻找执行以下操作的 Pythonic 方式:

我有一个键列表和一个对象列表。

对于任何键,都应该对适合该键的第一个对象进行处理。

如果没有对象适合任何键,所以根本没有做任何事情,应该做一些不同的事情。

我按如下方式实现了它,它工作正常:

didSomething = False
for key in keys:
    for obj in objects:
        if <obj fits to key>:
            doSomething(obj, key)
            didSomething = True
            break
if not didSomething:
    doSomethingDifferent()

但通常情况下,如果只有一个 for 循环,则不需要这样一个临时布尔值来检查某事是否已完成。您可以改用 for-else 语句。但这不适用于 2 个 for 循环,是吗?

我觉得应该有更好的方法来做到这一点,但我不明白。你有什么想法还是没有改进?

谢谢你:)

【问题讨论】:

  • for else 的问题在于,您永远不可能从键上的循环中中断,因此您将无法将其用作do_something_different 的信号。所以这看起来很好。
  • 您想让break 跳出外循环和内循环吗?如果是这样,那是不可能的(没有一个标志和两个breaks),这是你真正的问题;如果您确实有办法跳出外部循环,那跳过外部else。另一方面,如果你不希望这样,那么else 是没有意义的——你永远不会跳出外循环——所以你的设计有问题,而不是语言的限制。
  • @abarnert:我只想打破内部循环来检查其他键。
  • 在这种情况下,您认为else 是什么意思?我不明白你在寻找什么语义。
  • @abarnert:我只是想检查一下,在 for 循环中是否已经完成了某些事情。如果没有,应该做一些不同的事情。如果只有 1 个 for 循环,则 for-else 语句正是这样做的。但我需要2个循环。所以我正在为内部for循环寻找类似外部for-else语句的东西^^

标签: python for-loop if-statement


【解决方案1】:

这并不真正适合for/else 范式,因为您不想打破外循环。因此,只需使用变量来跟踪是否已完成某项操作,就像在您的原始代码中一样。

使用单个表达式来查找第一个匹配对象,而不是第二个循环。请参阅Python: Find in list 了解执行此操作的方法。

didSomething = false
for key in keys:
    found = next((obj for obj in objects if <obj fits to key>), None)
    if found:
        doSomething(found, key)
        didSomething = true
if not didSomething:
    doSomethingDifferent()

【讨论】:

  • 我认为代码的目的是为匹配的 每个 键运行doSomething,而不仅仅是第一个键。如果没有匹配的键,doSomethingDifferent 应该运行。
  • @Blender:没错。我无法在第一场比赛中打破外循环。
  • 那么这个真的不适合for/else这个模型,只能在你跳出循环的时候使用。
【解决方案2】:

每当您发现自己需要跳出嵌套循环时,通常很难仔细考虑细节,而当您弄清楚时,答案通常是不可能的(或者至少只有通过显式标记变量或异常或其他模糊逻辑的东西)。

对此有一个简单的答案(如果有人通过搜索找到此问题,我将在下面提供答案),但这实际上不是您的问题。您要检查的不是“我是否正常完成循环”,因为您总是正常完成循环。您要检查的是“我做了什么(在这种情况下,请致电doSomething)一次或多次”。

这与外循环无关,不像打破外循环(显然是这样),所以它没有语法。您需要跟踪您是否做过一次或多次某事,而您已经在做的方式可能是最简单的方式。


在某些情况下,您可以重新排列事物以使循环变平或反转,因此您最终会一次使用所有当前外部值做一件事并打破该循环,在这种情况下,它是关于再次循环.但是,如果这严重扭曲了你的逻辑,以至于不再清楚发生了什么,那将不会是一种改进。例如:

fits = set()
for key in keys:
    for obj in objects:
        if <obj fits to key>:
            fits.add((obj, key))
for obj, key in fits:
    do_something(obj, key)
if not fits:
    do_something_else()

这可以简化:

fits = {(obj, key) for key in keys for obj in objects if <obj fits to key>}
for obj, key in fits:
    do_something(obj, key)
if not fits:
    do_something_else()

但是,无论哪种方式,请注意,我避免存储表明您是否找到适合的标志的方法是存储一组您找到的所有适合。对于一些问题,这是一种改进。但是,如果该集合可能非常大,那将是一个糟糕的主意。如果这个集合在概念上对你的问题没有任何意义,它可能会混淆逻辑而不是简化它。


如果您的问题 超出了嵌套循环(它不是,但同样,它可能是针对通过搜索找到此问题的其他人),总有一个简单的答案为此:只需将整个循环嵌套并将其重构为一个函数。然后,您只需使用return,就可以在任何级别突破。如果你没有在任何地方return,循环之后的代码将会运行,而如果你做了return,它会——就像else一样。

所以:

def fits():
    for key in keys:
        for obj in objects:
            if <obj fits to key>:
                doSomething(obj, key)
                return
    doSomethingDifferent()

fits()

如果两个循环都是你想要的,我不确定是否中断。如果是,这正是您想要的。如果不是,它不会,但是我不确定你在寻找 else 的什么语义——它应该什么时候运行——所以我不知道如何解释如何做到这一点。

完成此操作后,您可能会发现抽象泛化到不仅仅是在您的代码中使用,因此您可以将函数转换为接受参数而不是使用闭包或全局变量的函数,并返回一个值或引发而不是调用两个函数之一,依此类推。但有时,你只需要这个微不足道的局部函数。

【讨论】:

  • 感谢您的建议!我会考虑使用一些内部函数。但是这种方式行不通,因为如果第一个键适合对象,您就会离开。但是随后我还必须检查其他键,并且 return 语句不会发生这种情况,例如,如果我有 5 个键,则最多可以调用 doSomething() 5 次。但只有当它被调用 0 次时,我才想调用 DoSomethingDifferent()。
  • 非常感谢您的详细回答!所以我接受没有隐藏的 python 函数可以阻止我使用临时变量来存储标志:D 你存储集合的方式看起来更紧凑但 imo,它不容易阅读。这就是为什么我认为,我会保持原样:)
  • @Max16hr 是的,这是在某些情况下不使用集合推导的另一个很好的理由,除了我给出的两个。每当您认为“我在 6 个月后可能会发现很难阅读此代码”时,请想出一些更冗长/明确/没有问题的东西,即使它感觉不那么聪明。
【解决方案3】:

没有真正的方法可以简化您的代码。但是,它的编写方式有点令人困惑。我实际上会更详细地确保它被正确阅读:

def fit_objects_to_keys(objects, keys):
    for key in keys:
        for obj in objects:
            if <obj fits to key>:
                yield obj, key
                break

none_fit = True

for obj, key in fit_objects_to_keys(keys, objects):
    doSomething(obj, key)
    none_fit = False

if none_fit:
    doSomethingDifferent()

如果你解释一下&lt;obj fits to key&gt; 的实际作用,你也许可以进一步简化它。

【讨论】:

  • 可能你是对的,无法简化。我想将 3 行(temp=false,temp=true,if temp)减少为 1 行(else)。所以我认为新功能不会简化代码:D
  • @Max16hr:如果增加几行代码的可读性,它们不会有什么坏处(鉴于您的问题的答案相互矛盾,您可以看到您的代码乍一看很容易被误读)。毕竟,人类将是阅读代码的人,Python 只是在评估它。
【解决方案4】:

我同意您的代码可以正常使用的评论 - 但是如果您必须将多个 for 循环扁平化为一个(以便您可以使用“else”功能,例如,或者for循环的数量本身是可变的),这实际上是可能的:

import itertools
for key, obj in itertools.product(keys, objects):
    if <obj fits to key>:
        doSomething(obj, key)
        break
else:
    doSomethingDifferent()

【讨论】:

    猜你喜欢
    • 2021-12-23
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多