python contextmanager 如何将异常重新引发回装饰生成器?

问题描述

这是一个关于 contextmanager 如何做它所做的事情的问题。

contextmanger一个装饰器,它调用被装饰的函数一个生成器)两次,以构建 __enter____exit__ 函数,由 with条款,到目前为止一切顺利。我不明白的是——当在 with 块内引发异常时,生成器内的 except 怎么能捕获它吗?

@contextmanager
def f():
    try:
        yield 'foo'
    except Exception as e:
        print('How can I ever reach here??')
        print(e)
    finally:
        print('finally')

with f() as p:
    print(p)
    raise Exception('bar')

输出

foo
How can I ever reach here??
bar
finally

我认为魔法发生在 @contextmanager 中,因为如果我移除装饰器,并且只在 try 块内执行“yield”,则生成器内部不会捕获生成器外部的异常:

def f():
    try:
        yield 'foo'
    except Exception as e:
        print('How can I ever reach here??')
        print(e)
    finally:
        print('finally')

g = f()
print(next(g))
raise Exception('bar')

输出

foo
Traceback (most recent call last):
...
Exception: bar

我查看了 contextlib.contextmanager 代码,但仍然无法弄清楚使用纯 python 代码如何做到这一点。关于我在这里错过的语言的一些基本知识?

解决方法

让您感到困惑的逻辑是 _GeneratorContextManager。您的函数 f 是 self.gen。代码刚刚调用了 next(self.gen),取回了字符串 "foo",并且正在等待。 f() 位于 yield 语句的中间。

此时您抛出异常。由于 python 看到您在 with 块中,(这些都内置在语言中),它调用生成器的 __exit__ 方法,并使用描述错误的参数。这就是上下文管理器的工作方式。上下文管理器调用 self.gen.throw,它通过抛出异常来恢复生成器。进去。瞧。您在异常处理程序中。

这样会更清楚吗?

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...