处理生成器中抛出的异常

时间:2021-06-01 20:30:01

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