我有一台发电机和消耗它的功能:
def read():
while something():
yield something_else()
def process():
for item in read():
do stuff
如果发电机抛出一个异常,我要处理的是,在消费函数,然后继续消耗迭代,直到它耗尽。 请注意,我不希望有发电机任何异常处理代码。
我想到是这样的:
reader = read()
while True:
try:
item = next(reader)
except StopIteration:
break
except Exception as e:
log error
continue
do_stuff(item)
但这个看起来相当尴尬的我。
当发电机抛出一个异常,它退出。 你不能继续消费其生成的项目。
例:
>>> def f():
... yield 1
... raise Exception
... yield 2
...
>>> g = f()
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f
Exception
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
如果控制发电机的代码,你可以处理发生器内的除外; 如果没有,你应该尽量避免异常的发生。
这也是我不知道如果我正确处理/优雅的东西。
我要做的就是要yield
一个Exception
从发电机,然后提高它在其他地方。 喜欢:
class myException(Exception):
def __init__(self, ...)
...
def g():
...
if everything_is_ok:
yield result
else:
yield myException(...)
my_gen = g()
while True:
try:
n = next(my_gen)
if isinstance(n, myException):
raise n
except StopIteration:
break
except myException as e:
# Deal with exception, log, print, continue, break etc
else:
# Consume n
这样,我仍然继续在异常不提高它,这将造成发电机功能停止。 主要的缺点是,我需要检查结果产生isinstance
在每次迭代。 我不喜欢一台发电机,可以产生不同类型的结果,但是用它作为最后的手段。
我有需要解决此问题几次和别人做了搜索后,在这个问题就来了。
扔代替加薪
一个选项-这将需要重构事情有点比特将是throw
在发电机(另一个错误处理发生器)例外,而不是raise
它。 这里是一个可能的样子:
def read(handler):
# the handler argument fixes errors/problems separately
while something():
try:
yield something_else()
except Exception as e:
handler.throw(e)
handler.close()
def err_handler():
# a generator for processing errors
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) # initialize error handler
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) # initialize handler
return handled_inner
def my_err_handler():
while True:
try:
yield
except MyError:
print("error handled")
# all other errors will bubble up here
@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:
# prevent the generator from closing after an Exception
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) # initialize handler
return handled_inner