Python: 使用2个for循环的else语句

3
我正在寻找适用于Python的方法来实现以下功能:
我有一个键列表和一个对象列表。
对于任何一个键,应该对符合该键的首个对象进行操作。
如果没有对象符合任何键,则根本不需要执行任何操作,应该执行其他不同的操作。
我按照以下方式实现了这个功能,它可以正常工作:
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语句。但是,对于两个for循环,这种方法就行不通了,对吗?

我感觉应该有更好的方法来处理这个问题,但我不知道。您有什么想法,还是说没有改进的余地?

谢谢:)


for else 的问题在于你永远没有可能从键的循环中跳出,因此你将无法将其用作“执行其他操作”的信号。所以现在看起来已经很好了。 - juanpa.arrivillaga
你想让 break 同时跳出外层循环和内层循环吗?如果是,那是不可能的(除非用一个标志以及两个 break),这才是真正的问题;如果你确实有一种方法可以跳出外层循环,那么将会跳过外部的 else。另一方面,如果你不想要那样做,那么 else 就没有意义了——你永远不会跳出外层循环——因此,这说明你的设计有问题,而不是语言的限制。 - abarnert
@abarnert:我只想中断内部循环,以便检查其他键。 - Max16hr
@abarnert: 这就是我想要的!我调用了函数一次还是多次?看来我必须使用临时变量。 - Max16hr
是的,在这种情况下,它真的与外部循环无关,因此没有办法在外部循环的语法中表达它。(这意味着我的答案和其他所有答案基本上都是无用的;我将删除或编辑我的答案。) - abarnert
显示剩余5条评论
4个回答

3

这不是很适合使用 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()

3
我认为这段代码的意图是对每一个匹配的键值都运行 doSomething,而不仅仅是第一个。如果没有键值匹配,则应该运行 doSomethingDifferent - Blender
@Blender:没错。我不能在第一次匹配时退出外层循环。 - Max16hr
1
那么这并不适用于 for/else 模型,该模型只能在你跳出循环时使用。 - Barmar

3
每当你需要跳出嵌套循环时,通常很难思考细节,当你想明白后,答案通常只是它是不可能的(或者至少只能通过显式标志变量、异常或其他模糊你的逻辑来实现)。
对此有一个简单的答案(我将在下面包含它,以防任何人通过搜索找到这个问题),但这实际上不是你的问题。你想要检查的不是“我是否正常完成了循环”,因为你总是正常完成循环。你想要检查的是“我是否做了一些事情(在这种情况下,调用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()。 - Max16hr
1
非常感谢您提供如此详细的答案!所以我接受了这个事实,即没有隐藏的Python函数可以防止我使用临时变量来存储标志:D 您存储集合的方式看起来更紧凑,但在我看来,它不容易阅读。这就是为什么我认为我会保持原样 :) - Max16hr
1
@Max16hr 是的,这是除了我提到的两个原因之外,不使用集合推导式的另一个好理由。每当你想到“半年后的我可能会发现阅读这段代码很困难”时,请想出更冗长/明确/其他的东西,避免这个问题,即使它感觉不那么聪明。 - abarnert

2

没有真正简化代码的方法。然而,它的书写方式有点混乱。为了确保正确阅读,我实际上会使它更冗长:

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

如果你解释一下<obj fits to key>的实际作用,可能会进一步简化它。


可能你是对的,这个问题可能无法简化。我想把3行代码(temp=false, temp=true, if temp)简化成1行(else)。所以我认为新函数并不能简化代码 :D - Max16hr
1
@Max16hr:如果添加一些额外的行可以显著提高代码的可读性,那么它们并不会有任何损失(考虑到您的问题有冲突的答案,您可以看到在第一次浏览时很容易误读您的代码)。毕竟,人类将是阅读代码的人,Python只是对其进行评估。 - Blender

1
我同意评论者的看法,认为你的代码已经很好了。但是如果你必须将多个for循环压缩成一个(例如,你想使用“else”功能,或者for循环的数量本身是可变的),这是可以实现的:
import itertools
for key, obj in itertools.product(keys, objects):
    if <obj fits to key>:
        doSomething(obj, key)
        break
else:
    doSomethingDifferent()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接