为何基于@contextmanager实现的上下文管理器遇异常不退出?
为什么用
@contextmanager实现的上下文管理器在异常时未关闭文件? 问题出在你用@contextmanager装饰的生成器没有处理with块内抛出的异常,导致yield之后的清理代码(关闭文件)根本没机会执行。
核心原因:@contextmanager的工作逻辑
@contextmanager装饰器是把生成器包装成上下文管理器的语法糖,它的执行规则是:
- 执行
yield之前的代码(对应类实现的__enter__) yield返回的对象绑定给as后的变量- 当
with块正常退出时,生成器会继续执行yield之后的代码;但如果with块内抛出异常,这个异常会直接传递给生成器——如果生成器没有捕获这个异常,yield之后的代码会被直接跳过,终止执行。
而你写的生成器里,with块抛出Exception后,异常直接打断了生成器,f.close()和print('closed')完全没运行,所以文件没被关闭,后续还能继续读取内容。
反观类实现的上下文管理器:__exit__方法是Python上下文管理器协议规定的必执行逻辑——不管with块是正常退出还是抛出异常,__exit__都会被调用,所以文件能被正常关闭。
修复方法
给生成器里的yield加上try/finally,确保清理逻辑无论是否发生异常都会执行:
from contextlib import contextmanager @contextmanager def open_read(path: str): f = open(path, 'r') print('open') try: yield f finally: f.close() print('closed')
这样修改后,不管with块内是正常执行还是抛出异常,finally块里的关闭代码都会运行,之后再读取文件就会触发ValueError: I/O operation on closed file.,符合你的预期。
内容的提问来源于stack exchange,提问作者Eyal Kutz
相关产品推荐
相关产品推荐

