@contextmanager是最轻量、最省代码、最容易维护的上下文管理器实现方式,通过生成器函数自动实现__enter__/__exit__协议,yield前获取资源、yield后(常配finally)释放资源,性能开销可忽略。

contextlib.contextmanager 不是“最快”的方法——它是最轻量、最省代码、最容易维护的开发效率最优解,但底层性能略低于手写类实现。
真正影响执行速度的关键在资源获取/释放逻辑本身,而非上下文管理器的包装形式。你花 10ms 打开数据库连接,contextmanager 额外带来的开销不到 0.1μs,完全可以忽略。
下面说清楚怎么用、为什么这么设计、以及哪些地方容易翻车。
什么时候该用 @contextmanager 而不是写类?
满足以下任意一条,就直接上 @contextmanager:
- 资源生命周期简单:打开/关闭、加锁/解锁、计时开始/结束
- 不需要在
__enter__中做复杂状态初始化(比如多步校验、依赖注入) - 不打算复用同一个上下文管理器实例多次(它每次
with都新建生成器) - 你不想暴露
__enter__/__exit__方法给调用方,也不需要继承或重载行为
@contextmanager 的 yield 位置决定资源可见范围
yield 前的代码等价于 __enter__,yield 后(尤其是 finally 块里)等价于 __exit__。但注意:
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
- yield 必须只出现一次,且不能在嵌套作用域(如 if/for 内部)中 yield
- 如果 yield 前抛异常,
finally不会执行(因为还没进到生成器暂停点) - yield 后的代码只有在 with 块退出后才运行;若 with 块内
return或break,仍会触发 yield 后逻辑 - 资源对象必须通过 yield 返回,否则
as xxx拿不到东西
@contextmanager
def db_conn(db_path):
conn = sqlite3.connect(db_path) # ← 这里出错,finally 不执行
try:
yield conn # ← conn 绑定给 as 后的变量
finally:
conn.close() # ← 这里必执行,哪怕 with 块 raise 了 Exception
和手写类比,@contextmanager 少写了什么、多承担了什么?
它帮你自动实现了:
- 符合上下文管理协议的
__enter__/__exit__方法签名 - 异常传播逻辑:with 块内未捕获的异常,会原样 re-raise 到生成器 yield 点,再由
try/except/finally处理 - 支持装饰器用法:
@my_context直接修饰函数,每次调用都新建上下文
但它不帮你做:
- 实例属性缓存(比如你想在多个
with块间共享某个句柄,得自己管生命周期) - 类型提示友好性:生成器函数返回类型是
Generator[...],不如类明确 - 调试时堆栈更难读:异常 traceback 会穿过生成器框架,不如类方法直观
真正容易被忽略的坑
有三个细节,90% 的人第一次写都会栽:
-
yield必须在try内部,但finally要包住整个try/except,否则异常跳过清理 - 不要在
yield后写可能抛异常的逻辑(比如再次conn.rollback()),除非你明确想吞掉那个异常 - 如果资源本身是异步的(比如
aiohttp.ClientSession),别硬套@contextmanager—— 该用@asynccontextmanager
手写类和 @contextmanager 在绝大多数业务场景下性能差异可以忽略。选哪个,取决于你更在意代码可读性、维护成本,还是对对象生命周期的完全控制。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










