@contextmanager将生成器函数包装为符合__enter__/__exit__协议的上下文管理器:yield前代码对应__enter__,yield值赋给as变量,yield后(须用try/finally包裹)对应__exit__行为。

contextmanager 装饰器到底做了什么
@contextmanager 不是魔法,它只是把一个生成器函数包装成符合 __enter__ / __exit__ 协议的上下文管理器类。核心逻辑是:生成器 yield 之前的部分当 __enter__ 执行,yield 的值就是 with 语句中 as 绑定的对象,yield 之后的代码(包括异常处理)则对应 __exit__ 的行为。
写一个能用的 contextmanager 生成器必须满足什么条件
常见错误是忘记 yield,或 yield 后没处理异常,导致资源泄露或未捕获的 RuntimeError。正确写法需满足:
- 函数必须是生成器(含
yield语句),且只能有一个yield -
yield前做“进入”操作(如打开文件、获取锁) -
yield后做“退出”清理(如关闭文件、释放锁),且必须用try/finally或except/finally包裹,否则异常会中断执行 - 若需在异常发生时做特殊处理(如忽略某类异常),可在
except块中返回True,这等价于在__exit__中返回True
示例:
from contextlib import contextmanager <p>@contextmanager def open_file(path): f = None try: f = open(path, 'r') yield f # ← 这个 yield 必须存在,且只出现一次 finally: if f is not None: f.close()
为什么 yield 后不加 try/finally 就会出问题
如果只写 yield f; f.close(),当 with 块中抛出异常,f.close() 根本不会执行——因为控制流直接跳出了生成器。更危险的是,Python 会报 RuntimeError: generator didn't yield,因为 @contextmanager 内部期望生成器完整运行到结束(哪怕是在异常路径下)。所以 finally 不是可选项,是强制要求。
立即学习“Python免费学习笔记(深入)”;
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
另一个易错点:在 yield 前就出错(比如 open() 失败),此时 finally 仍会执行,但 f 可能是 None,所以清理逻辑要判空或放在 try 外层初始化后立即做防御。
contextmanager 和手动写 __enter__/__exit__ 类比起来差在哪
性能上几乎无差别,@contextmanager 底层也是构造一个轻量类实例;主要差异在可读性和错误容忍度:
- 手动实现类更灵活(比如支持多次 enter/exit、状态重用),但代码量多、易漏
__exit__返回值逻辑 -
@contextmanager更适合“一次性资源封装”,逻辑集中、不易出错,但无法复用同一个实例多次进入 - 调试时注意:
@contextmanager生成的上下文管理器对象没有源码级可读的__enter__方法,其行为全由生成器驱动,堆栈里看到的是_GeneratorContextManager.__enter__
真正复杂的状态管理(如数据库连接池、嵌套事务)建议直接写类;简单资源封装(临时目录、计时器、日志前缀)用 @contextmanager 更快也更稳。
最常被忽略的一点:生成器函数本身不能带参数校验逻辑——所有参数检查必须放在 yield 前,否则异常发生在 __enter__ 阶段之外,__exit__ 不会被调用,资源不会清理。这个边界得心里有数。

















