geo*_*org 46 python exception generator
我有一个生成器和一个消耗它的函数:
def read():
while something():
yield something_else()
def process():
for item in read():
do stuff
Run Code Online (Sandbox Code Playgroud)
如果生成器抛出异常,我想在使用者函数中处理它,然后继续使用迭代器直到它耗尽.请注意,我不希望在生成器中有任何异常处理代码.
我想到了类似的东西:
reader = read()
while True:
try:
item = next(reader)
except StopIteration:
break
except Exception as e:
log error
continue
do_stuff(item)
Run Code Online (Sandbox Code Playgroud)
但这对我来说相当尴尬.
Sve*_*ach 54
当生成器抛出异常时,它会退出.您无法继续使用它生成的项目.
例:
>>> 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
Run Code Online (Sandbox Code Playgroud)
如果你控制生成器代码,你可以在生成器内处理异常; 如果没有,你应该尽量避免发生异常.
这也是我不确定我是否正确/优雅地处理的事情.
我做的是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
Run Code Online (Sandbox Code Playgroud)
这样我仍然可以在没有提升的情况下继续执行Exception,这会导致生成器功能停止.主要缺点是我需要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
Run Code Online (Sandbox Code Playgroud)
这并不总是最好的解决方案,但它肯定是一种选择。
你可以用装饰器让这一切变得更好:
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()
Run Code Online (Sandbox Code Playgroud)
输出:
0
1
2
error handled
3
4
5
6
7
8
9
Run Code Online (Sandbox Code Playgroud)
然而,这样做的缺点是您仍然必须Exception在可能产生错误的生成器中进行通用处理。不可能解决这个问题,因为在生成器中引发任何异常都会关闭它。
最好有某种yield raise声明,它允许生成器在错误发生后继续运行。然后你可以写这样的代码:
@handled(my_err_handler())
def read():
i = 0
while i<10:
yield i
i += 1
if i == 3:
yield raise MyError()
Run Code Online (Sandbox Code Playgroud)
...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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
23733 次 |
| 最近记录: |