处理在发电机抛出的异常处理在发电机抛出的异常(Handle an exception thrown

2019-06-14 15:57发布

我有一台发电机和消耗它的功能:

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)

但这个看起来相当尴尬的我。

Answer 1:

当发电机抛出一个异常,它退出。 你不能继续消费其生成的项目。

例:

>>> 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

如果控制发电机的代码,你可以处理发生器内的除外; 如果没有,你应该尽量避免异常的发生。



Answer 2:

这也是我不知道如果我正确处理/优雅的东西。

我要做的就是要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在每次迭代。 我不喜欢一台发电机,可以产生不同类型的结果,但是用它作为最后的手段。



Answer 3:

我有需要解决此问题几次和别人做了搜索后,在这个问题就来了。


扔代替加薪

一个选项-这将需要重构事情有点比特将是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


文章来源: Handle an exception thrown in a generator