I've got a generator and a function that consumes it:
我有一个生成器和一个消耗它的函数:
def read():
while something():
yield something_else()
def process():
for item in read():
do stuff
If the generator throws an exception, I want to process that in the consumer function and then continue consuming the iterator until it's exhausted. Note that I don't want to have any exception handling code in the generator.
如果生成器抛出异常,我想在使用者函数中处理它,然后继续使用迭代器直到它耗尽。请注意,我不希望在生成器中有任何异常处理代码。
I thought about something like:
我想到了类似的东西:
reader = read()
while True:
try:
item = next(reader)
except StopIteration:
break
except Exception as e:
log error
continue
do_stuff(item)
but this looks rather awkward to me.
但这对我来说相当尴尬。
3 个解决方案
#1
42
When a generator throws an exception, it exits. You can't continue consuming the items it generates.
当生成器抛出异常时,它会退出。您无法继续使用它生成的项目。
Example:
例:
>>> 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
If you control the generator code, you can handle the exception inside the generator; if not, you should try to avoid an exception occurring.
如果你控制生成器代码,你可以处理生成器内的异常;如果没有,你应该尽量避免发生异常。
#2
5
This is also something that I am not sure if I handle correctly/elegantly.
这也是我不确定我是否正确/优雅地处理的事情。
What I do is to yield
an Exception
from the generator, and then raise it somewhere else. Like:
我所做的是从生成器中生成一个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
This way I still carry over the Exception without raising it, which would have caused the generator function to stop. The major drawback is that I need to check the yielded result with isinstance
at each iteration. I don't like a generator which can yield results of different types, but use it as a last resort.
这样我仍然可以在不提高它的情况下继续执行Exception,这会导致生成器功能停止。主要缺点是我需要在每次迭代时检查isinstance的结果。我不喜欢可以产生不同类型结果的发电机,但是将它作为最后的手段使用。
#3
4
I have needed to solve this problem a couple of times and came upon this question after a search for what other people have done.
我需要解决这个问题几次,并在搜索了其他人所做的事后发现了这个问题。
One option- which will require refactoring things a little bit- would be to throw
the exception in the generator (to another error handling generator) rather than raise
it. Here is what that might look like:
一个选项 - 需要稍微重构一些东西 - 将异常抛出到生成器(到另一个错误处理生成器)而不是提升它。这可能是这样的:
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()
for item in read(handler):
do stuff
This isn't always going to be the best solution, but it's certainly an option.
这并不总是最好的解决方案,但它肯定是一种选择。
EDIT:
编辑:
You could make it all just a bit nicer with a decorator this way (I haven't tested this but it should work, EDIT: not working; I'll fix it later, but the idea is sound):
你可以通过这种方式使装饰器变得更好(我没有测试过但是它应该可以工作,编辑:不工作;我稍后会修复它,但想法是合理的):
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:
yield from g
except Exception as e:
handler.throw(e)
return wrapper
return handled_inner
@handled(err_handler())
def read():
while something():
yield something_else()
def process():
for item in read():
do stuff
#1
42
When a generator throws an exception, it exits. You can't continue consuming the items it generates.
当生成器抛出异常时,它会退出。您无法继续使用它生成的项目。
Example:
例:
>>> 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
If you control the generator code, you can handle the exception inside the generator; if not, you should try to avoid an exception occurring.
如果你控制生成器代码,你可以处理生成器内的异常;如果没有,你应该尽量避免发生异常。
#2
5
This is also something that I am not sure if I handle correctly/elegantly.
这也是我不确定我是否正确/优雅地处理的事情。
What I do is to yield
an Exception
from the generator, and then raise it somewhere else. Like:
我所做的是从生成器中生成一个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
This way I still carry over the Exception without raising it, which would have caused the generator function to stop. The major drawback is that I need to check the yielded result with isinstance
at each iteration. I don't like a generator which can yield results of different types, but use it as a last resort.
这样我仍然可以在不提高它的情况下继续执行Exception,这会导致生成器功能停止。主要缺点是我需要在每次迭代时检查isinstance的结果。我不喜欢可以产生不同类型结果的发电机,但是将它作为最后的手段使用。
#3
4
I have needed to solve this problem a couple of times and came upon this question after a search for what other people have done.
我需要解决这个问题几次,并在搜索了其他人所做的事后发现了这个问题。
One option- which will require refactoring things a little bit- would be to throw
the exception in the generator (to another error handling generator) rather than raise
it. Here is what that might look like:
一个选项 - 需要稍微重构一些东西 - 将异常抛出到生成器(到另一个错误处理生成器)而不是提升它。这可能是这样的:
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()
for item in read(handler):
do stuff
This isn't always going to be the best solution, but it's certainly an option.
这并不总是最好的解决方案,但它肯定是一种选择。
EDIT:
编辑:
You could make it all just a bit nicer with a decorator this way (I haven't tested this but it should work, EDIT: not working; I'll fix it later, but the idea is sound):
你可以通过这种方式使装饰器变得更好(我没有测试过但是它应该可以工作,编辑:不工作;我稍后会修复它,但想法是合理的):
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:
yield from g
except Exception as e:
handler.throw(e)
return wrapper
return handled_inner
@handled(err_handler())
def read():
while something():
yield something_else()
def process():
for item in read():
do stuff