Python:如何告诉for循环从函数继续执行?

28

有时在for循环中需要以下模式。有时在同一循环中发生多次:

try:
    # attempt to do something that may diversely fail
except Exception as e:
    logging.error(e)
    continue

现在我没有看到一个好的方法来将此内容封装为函数,因为它不能返回 continue

def attempt(x):
    try:
        raise random.choice((ValueError, IndexError, TypeError))
    except Exception as e:
        logging.error(e)
        # continue  # syntax error: continue not properly in loop
        # return continue  # invalid syntax
        return None  # this sort of works

如果我使用 return None,那么我可以:

a = attempt('to do something that may diversely fail')
if not a:
    continue

但我觉得这并不能完全说明问题。我想告诉for循环从attempt函数内部continue(或伪造它)。


4
为什么要把四行简单的代码[try-except-log-continue]包装成不太透明的东西? - NPE
7
在函数内部引发异常然后在循环中捕获它有什么问题吗? - rzetterberg
1
你做错了。如果你正在使用 for 循环作为一种通用模式,请将 包装在一个函数中,而不是循环的元素。 - Andrei Sosnin
1
几年后再看,“except Exception:”或“except Exception as e:”是最恶劣的Python反模式。https://realpython.com/blog/python/the-most-diabolical-python-antipattern/ - fmalina
9个回答

16

Python已经有一个非常好的结构来完成这个任务,它不使用continue

for i in range(10):
    try:
        r = 1.0 / (i % 2)
    except Exception, e:
        print(e)
    else:
        print(r)

我不会再嵌套更多了,否则你的代码很快就会变得非常丑陋。

在你的情况下,我可能会像这样做,因为单独测试每个函数和扁平化比嵌套更好:扁平优于嵌套

#!/usr/bin/env python

def something_that_may_raise(i):
    return 1.0 / (i % 2)

def handle(e):
    print("Exception: " + str(e))

def do_something_with(result):
    print("No exception: " + str(result))

def wrap_process(i):
    try:
        result = something_that_may_raise(i)
    except ZeroDivisionError, e:
        handle(e)
    except OverflowError, e:
        handle(e) # Realistically, this will be a different handler...
    else:
        do_something_with(result)

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

记住要始终捕获具体的异常。如果你没有预期到一个具体的异常被抛出,那么继续处理循环可能是不安全的。

编辑以下评论:

如果你真的不想处理异常,虽然我认为这是一个坏主意,那么就捕获所有异常(except:),而不是handle(e),只需pass。此时,wrap_process()将结束,跳过了真正完成工作的else:块,你将进入下一次for循环迭代。

请记住,错误不应该悄无声息地传递


1
@Frank Malina:很抱歉听到这个。哪一部分不符合您的要求?在我看来,else: 是解决这种情况最Pythonic的方式。 - johnsyweb
我不想处理异常,我只想指示程序在异常出现时继续执行最近封闭循环的下一个周期。 - fmalina
1
@Frank Malina:如果循环中的所有其他内容都在else:块中(为了整洁而用do_something_with()包装),那么这将跳过循环的其余部分并像您希望的那样进入下一个循环。尝试运行我的示例代码以获得更充分的演示。 - johnsyweb
1
@Frank Malina,看起来Johnsyweb的回答完全符合你在问题中描述的要求。你编写的代码和Johnsyweb提供的代码在Python中的作用是相同的。你真的应该尝试一下或仔细审查它。 - Andrei Sosnin
1
@Frank Malina:有时候你必须要消除特定的错误。记录日志并不等同于消除错误。 - johnsyweb
显示剩余5条评论

8
整个异常的概念是它们可以在多级间接性中工作,即如果您在调用层次结构的深处出现错误(或任何其他异常状态),您仍然可以在更高层次上捕获它并正确处理。
在您的情况下,假设您有一个函数attempt(),它调用了调用层次结构中的函数attempt2()和attempt3(),而attempt3()可能遇到需要导致主循环终止的异常状态。
class JustContinueException(Exception):
    pass

for i in range(0,99):
    try:
        var = attempt() # calls attempt2() and attempt3() in turn
    except JustContinueException:
        continue # we don't need to log anything here
    except Exception, e:
        log(e)
        continue

    foo(bar)

def attempt3():
    try:
        # do something
    except Exception, e:
        # do something with e, if needed
        raise # reraise exception, so we catch it downstream

你甚至可以自己抛出一个虚拟异常,这只会导致循环终止,并且不会被记录在日志中。

def attempt3():
    raise JustContinueException()

你仍然可以在更高的层面上捕获它并妥善处理,非常有用的提醒 :) - J0ANMM

4
除了上下文,我只想简洁地回答这个问题。不,一个函数不能让它所在的循环继续执行。这是因为它没有关于这个上下文的信息。另外,如果该函数在没有处理 continue 的循环的情况下被调用一定会出现新的问题。
但是,函数可以通过各种方式向调用者发出信号,要求它继续执行任何当前正在进行的循环。当然,一种方式是返回值。例如,返回 FalseNone 来发出此信号。另一种方法是引发一个特殊的 Exception
class ContinuePlease(Exception): pass

def f():
    raise ContinuePlease()

for i in range(10):
    try:
        f()
    except ContinuePlease:
        continue

3
也许你想要使用continuations?你可以去看看Eric Lippert的解释(如果你准备好了,你的思维将会被颠覆),但在Python中,它可能看起来有点像这样:
def attempt(operation, continuation):
    try:
        operation()
    except:
        log('operation failed!')
    continuation()

在你的循环中,你可以这样做:
attempt(attempt_something, lambda: foo(bar)) # attempt_something is a function

2

想象一下,您正在将 foo 映射到所有尝试成功的项目上。因此,attempt 是一个过滤器,很容易将其编写为生成器:

def attempted( items ):
    for item in items:
        try:
            yield attempt( item )
        except Exception, e:
            log(e)

print [foo(bar) for bar in attempted( items )]

2
你可以使用这个:
for l in loop:
  attempt() and foo(bar)

但是你应该确保attempt()返回True或False。

不过,说实话,Johnsyweb的答案可能更好。


这是一个不错的方法。非常有用。 - Vitalis

1

我通常不会发表第二个答案,但如果您真的不喜欢我的第一个答案,这是一种替代方法。

请记住,函数可以返回一个元组

#!/usr/bin/env python

def something_that_mail_fail(i):
    failed = False
    result = None
    try:
        result = 1.0 / (i % 4)
    except:
        failed = True # But we don't care
    return failed, result

for i in range(20):
    failed, result = something_that_mail_fail(i)
    if failed:
        continue
    for rah in ['rah'] * 3:
        print(rah)
    print(result)

我认为try ... except ... else是正确的方式,但你不应该默默地忽略错误。 Caveat emptor等等。


除了返回元组(True, None)而不是仅返回False之外,这与我在问题中提到的并没有太大的区别。这并没有指示for循环从函数内部继续执行。 - fmalina
很抱歉,我无法帮助您。Python中没有goto语句。这是正确的做法。 - johnsyweb

0

在try, except块之外尝试for循环

这个答案考虑了Python 3.4,但是在新版本中有更好的方法。这是我的建议:

import sys
if '3.4' in sys.version:
    from termcolor import colored
def list_attributes(module_name): '''在调用此函数之前导入模块。''' for index, method in enumerate(dir(module_name)): try: method = str(method) module = 'email' expression = module + '.' + method print('*' * len(expression), '\n') print( str(index).upper() + '. ',colored( expression.upper(), 'red'), ' ', eval( expression ).dir() , '...' , '\n'2 ) print('' * len(expression), '\n') print( eval( expression + '.doc' ), '\n'*4, 'END OF DESCRIPTION FOR: ' + expression.upper(), '\n'*4) except (AttributeError, NameError): continue else: pass finally: pass


这样做的效率不是很低吗,特别是如果你想对列表对象应用多个函数的话?每次想使用这样的函数时都必须遍历整个列表,然而如果你有一个返回 continue 的函数,你可以只遍历一次列表,并将该函数应用于列表的每个元素。 - Tim Kirkwood
1
Python 3.10语法解释了生成器,特别是yield语句如何实现你要求的功能。我为Python 3.4编写了这个答案。当然,如果你想扩展像这样的代码,你应该使用一个专为此任务优化的包,然而对于小规模的非专业用户来说,这个方法也可以胜任。https://docs.python.org/3/reference/grammar.html - Schopenhauer

-4

编辑:我刚才说的那些蠢话都删掉了...

最终的解决方案是重写整个程序,这样我就不需要像那样编码了。


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