我曾多次需要解决这个问题,在搜索了其他人的解决方案后找到了这个问题。
使用throw而不是raise
一种选择是稍微重构一下代码,将异常在生成器中 throw
给另一个错误处理生成器而不是 raise
。下面是可能的实现方式:
def read(handler):
while something():
try:
yield something_else()
except Exception as e:
handler.throw(e)
handler.close()
def err_handler():
while True:
try:
yield
except Exception1:
handle_exc1()
except Exception2:
handle_exc2()
except Exception3:
handle_exc3()
except Exception:
raise
def process():
handler = err_handler()
handler.send(None)
for item in read(handler):
do stuff
这并不总是最佳解决方案,但肯定是一种选择。
通用解决方案
您可以使用装饰器使其更加美观:
class MyError(Exception):
pass
def handled(handler):
"""
A decorator that applies error handling to a generator.
The handler argument received errors to be handled.
Example usage:
@handled(err_handler())
def gen_function():
yield the_things()
"""
def handled_inner(gen_f):
def wrapper(*args, **kwargs):
g = gen_f(*args, **kwargs)
while True:
try:
g_next = next(g)
except StopIteration:
break
if isinstance(g_next, Exception):
handler.throw(g_next)
else:
yield g_next
return wrapper
handler.send(None)
return handled_inner
def my_err_handler():
while True:
try:
yield
except MyError:
print("error handled")
@handled(my_err_handler())
def read():
i = 0
while i<10:
try:
yield i
i += 1
if i == 3:
raise MyError()
except Exception as e:
yield e
def process():
for item in read():
print(item)
if __name__=="__main__":
process()
输出:
0
1
2
error handled
3
4
5
6
7
8
9
然而,这样做的缺点是您仍然需要在生成器内部放置通用的Exception
处理程序,以便处理可能发生的错误。由于在生成器中引发任何异常都会将其关闭,因此无法避免这种情况。
一个想法的核心
如果有一种yield raise
语句可以让生成器在错误被引发后继续运行,那就太好了。然后,您可以编写如下代码:
@handled(my_err_handler())
def read():
i = 0
while i<10:
yield i
i += 1
if i == 3:
yield raise MyError()
...而handler()
装饰器可能看起来像这样:
def handled(handler):
def handled_inner(gen_f):
def wrapper(*args, **kwargs):
g = gen_f(*args, **kwargs)
while True:
try:
g_next = next(g)
except StopIteration:
break
except Exception as e:
handler.throw(e)
else:
yield g_next
return wrapper
handler.send(None)
return handled_inner