@contextmanager通过yield分离资源获取与清理,简化上下文管理;但初始化异常时清理不执行,多资源嵌套宜用ExitStack或独立with。

为什么直接写 try/except/finally 容易出错
手动配对 open() 和 close()、lock.acquire() 和 lock.release(),稍不注意就会漏掉 finally 块,或在异常路径里提前 return 导致资源没释放。更麻烦的是,多个资源嵌套时缩进爆炸,逻辑分散——比如先开文件再连数据库,任一失败都要确保已打开的资源被清理。
@contextmanager 怎么把“打开-使用-关闭”压成一层
它不要求你写类,只需一个带 yield 的函数:yield 之前做获取资源(setup),之后做清理(teardown)。Python 在 with 块退出时自动执行 yield 后的代码,无论是否异常。
常见错误:在 yield 前抛异常,yield 后的清理不会运行——所以初始化逻辑要尽量轻量,或自己包一层 try。
- 必须用
try/except包住yield才能捕获with块内的异常(用于日志、转换异常类型等) - 若想支持
__exit__返回True来吞掉异常,得在yield后加except并return True -
yield只能出现一次;若需返回值,用yield resource,with语句中用as接收
from contextlib import contextmanager
<p>@contextmanager
def managed_file(name):
f = open(name, 'w')
try:
yield f
finally:
f.close() # 即使 f.write() 抛异常也执行</p><h1>使用</h1><p>with managed_file('out.txt') as f:
f.write('hello')
</p>
什么时候不该用 @contextmanager,而该写类
当需要在进入时检查状态(如锁是否已被持有时抛 RuntimeError),或要重用同一个上下文管理器实例多次(类可保存状态),或清理逻辑依赖 yield 时传入的参数(比如根据 yield 返回值决定关哪个 socket),这时类更清晰。
性能上无实质差异,但类能明确分离 __enter__ / __exit__,调试时堆栈更干净;@contextmanager 的生成器帧会留在调用链里,出错时堆栈略深。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 简单资源(文件、临时目录、单次连接)优先用
@contextmanager - 需要支持
reentrant(可重入锁)、或__enter__返回不同对象(如threading.RLock每次返回自身)时,必须用类 - 如果清理动作本身可能抛异常,且你不希望它掩盖原始异常,得在
finally里用sys.exc_info()保存再处理
嵌套多个上下文管理器的坑与解法
Python 3.1+ 支持 with a(), b(): 语法,但所有 __enter__ 都成功后才进块;任一失败,已成功的会按逆序调用 __exit__。问题在于:如果 a().__enter__() 成功,b().__enter__() 失败,a.__exit__() 会收到 (exc_type, exc_val, tb) —— 但它本不该处理 b 的异常。
正确做法:每个资源独立管理,避免强耦合初始化顺序。
- 别在一个
@contextmanager里硬塞多个open(),拆成多个with嵌套或用contextlib.ExitStack - 要用
ExitStack动态添加资源(比如循环打开 N 个文件),它保证全部__exit__被调用,且不传播中间异常 - 注意
ExitStack.enter_context()返回值就是__enter__结果,可直接赋值
复杂点永远在异常传播路径上:谁该吞异常、谁该透传、清理时新异常是否覆盖旧异常——这些细节不会因为用了 @contextmanager 就自动理清。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










