这是一个关于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块内的收益率",生成器外部的异常不会在生成器内部捕获:
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
通过引发该异常来恢复生成器。进去。 瞧。 您位于异常处理程序中。
这是否使它更清楚?