Python:如何让for循环从函数继续?
有时候我在一个 for
循环里需要用到下面这种模式。有时候在同一个循环里还需要用好几次:
try:
# attempt to do something that may diversely fail
except Exception as e:
logging.error(e)
continue
现在我觉得没有一个好的办法把这个包裹成一个函数,因为它不能 return 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
但是我觉得这样做不太合适。我想在 attempt
函数里告诉这个 for 循环去 continue
(或者说假装继续)。
9 个回答
除了上下文,我想简单回答这个问题。首先,函数不能让它被调用的循环继续。这是因为函数不知道这个循环的情况。而且,如果函数在没有循环的情况下被调用,那就会引发一系列新的问题,比如这个函数该怎么处理这个continue
。
但是,函数可以通过不同的方式来“通知”调用它的地方,表示它希望继续当前的循环。其中一种方式就是返回值。例如,返回False
或None
来表示这个意思。另一种通知方式是抛出一个特殊的Exception
:
class ContinuePlease(Exception): pass
def f():
raise ContinuePlease()
for i in range(10):
try:
f()
except ContinuePlease:
continue
异常的主要概念是,它们可以在多个层级之间传递。也就是说,如果你在调用的某个深层次的函数里遇到了错误(或者其他特别的情况),你仍然可以在更高的层级捕捉到这个错误,并且妥善处理它。
举个例子,假设你有一个叫做 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()
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
循环的下一次迭代。
请记住,错误不应该悄无声息地过去。