我有以下代码:
import contextlib
import sys
class CC:
def __enter__(self):
pass
def __exit__(self, *args):
return True
@contextlib.contextmanager
def ctx():
try:
with CC():
yield
except:
print("has exception")
raise
finally:
print("finally")
print(sys.exc_info())
def test():
with ctx():
raise ValueError()
test()
这有点令人费解。但我的想法是在上下文ctx
中提出一个异常。因为CC
在退出时返回True
,所以应该抑制异常。因此,我预计except
子句不会执行,finally
子句中的sys.exc_info()
不会打印任何内容。
但实际情况是,except
子句不执行,但finally
子句中的sys.exc_info()
打印原始异常。这让我很困惑,因为__exit__
返回True不是应该抑制异常吗?为什么except
和sys.exc_info()
看到的异常之间存在差异?
我找到了问题的答案。
因此,在上下文管理器(使用@contextmanager
或显式定义的__exit__
(中,当出现异常时,它会从yield
中抛出。基本上,代码的其余部分在抛出代码的except
中执行。上下文管理器当然可以捕获并抑制重新引发的异常,但它不会改变它在except
子句中的事实,因此它仍然在处理原始异常。这意味着,即使重新引发的异常被捕获并抑制,sys.exc_info()
仍将显示原始异常。
举个例子,这意味着
@contextmanager
def ctx():
try:
try:
yield
except:
pass
except:
assert False
finally:
print(sys.exc_info())
with ctx():
raise ValueError()
将在finally
中打印原始ValueError
,尽管外部except
不会捕获任何内容。
此外,我认为这意味着上下文管理器并不是代码重构的完全替代品。
with ctx:
try:
foo()
except:
...
不能总是转换成
@contextmanager
def fun():
with ctx:
try:
yield
except:
...
with fun():
do_something()