捕获Python中生成器调用者抛出的异常

时间:2017-06-16 21:57:17

标签: python python-2.7 generator coroutine

我正在尝试捕获生成器调用者抛出的异常:

class MyException(Exception):
    pass

def gen():
    for i in range(3):
        try:
            yield i
        except MyException:
            print 'handled exception'

for i in gen():
    print i
    raise MyException

此输出

$ python x.py
0
Traceback (most recent call last):
  File "x.py", line 14, in <module>
    raise MyException
__main__.MyException

当我打算输出

$ python x.py
0
handled exception
1
handled exception
2
handled exception

回想起来,我认为这是因为调用者与生成器有不同的堆栈,因此异常不会冒泡到生成器。 这是对的吗?是否有其他方法可以捕获调用者中引发的异常?

除了:我可以使用generator.throw()使其工作,但这需要修改调用者:

def gen():
    for i in range(3):
        try:
            yield i
        except MyException:
            print 'handled exception'
            yield

import sys
g = gen()
for i in g:
    try:
        print i
        raise MyException
    except:
        g.throw(*sys.exc_info())

2 个答案:

答案 0 :(得分:5)

您可能会想到,当执行器在生成器中命中yield时,生成器会执行for循环的主体,类似于带有yield的Ruby函数和块。这不是Python的工作方式。

当执行命中yield时,生成器的堆栈帧被挂起并从堆栈中删除,并且控制返回到(隐式)调用生成器next的代码方法。然后该代码进入循环体。在引发异常时,生成器的堆栈帧不在堆栈中,并且异常在发生气泡时不会通过生成器。

生成器无法响应此异常。

答案 1 :(得分:0)

您可能还因为上下文管理器(yield中的contextlib.contextmanager而感到困惑-正如我刚刚到达这里一样。 它们的用法可以是:

from contextlib import contextmanager


@contextmanager
def mycontext():
    try:
        yield
    except MyException:
        print 'handled exception'

因此,针对上述情况的类似解决方案是:

def gen():
    for i in range(3):
        yield i


for ii in gen():
    with mycontext():
        print ii
        raise MyException

给出预期的输出并使用所有收益。

在这里参加聚会有点晚,但是也许某个心中有类似想法的人会发现这很有帮助。

注意:将上下文放在生成器内部将与在其中使用try ... except一样是错误的!!