@contextmanager能简化上下文管理器编写,避免手动实现__enter__/__exit__时遗漏异常传播逻辑;yield前为进入逻辑,后为退出逻辑,天然隔离;嵌套用with语句,动态场景用exitstack;生成器实例不可重入。

用 @contextmanager 替代 __enter__/__exit__ 写法
直接写类的 __enter__ 和 __exit__ 容易漏掉异常传播逻辑,比如没加 return False 导致异常被吞,或忘记处理 exc_type 为 None 的正常退出场景。@contextmanager 把上下文逻辑压进一个生成器函数里,yield 之前是 __enter__,之后是 __exit__,天然隔离两段逻辑。
实操建议:
- yield 必须有且仅有一个,返回值会成为
as后的变量 - yield 后的代码在退出时执行,无论是否发生异常;若需区分,检查
exc_type is not None - 如果想让异常继续向上抛,不要 return 或 return False;想抑制异常,return True
- 别在 yield 后面写可能抛异常的清理逻辑——它本身就在异常处理路径里,再抛会掩盖原始异常
from contextlib import contextmanager <p>@contextmanager def db_transaction(conn): conn.begin() try: yield conn # 进入时返回 conn except Exception: conn.rollback() raise # 原异常继续抛出 else: conn.commit() # 无异常才提交 </p>
嵌套多个上下文时避免手动 __enter__/__exit__ 调用
手写类上下文管理器遇到嵌套(比如文件+数据库+锁),就得层层调 __enter__、记返回值、再逆序调 __exit__,极易出错。用 @contextmanager 可以把嵌套收束成一次 yield,外部仍用标准 with 语法。
实操建议:
- 不要在生成器里手动调其他上下文管理器的
__enter__/__exit__ - 改用
with语句嵌套,@contextmanager函数内部可安全使用 - 若需动态决定嵌套哪些上下文,用
ExitStack(见下一条)
@contextmanager
def transactional_file_and_db(filename, conn):
with open(filename) as f, db_transaction(conn) as tx:
yield f, tx # 一次返回两个资源
需要动态构造上下文时用 ExitStack
当上下文数量不确定(比如根据配置打开 N 个文件)、或部分上下文可能跳过(条件判断后才决定是否 enter),硬写类或 @contextmanager 都会很别扭。ExitStack 提供运行时注册和自动退出的能力,比手写状态机可靠得多。
实操建议:
- 用
stack.enter_context(cm)替代手动cm.__enter__(),失败时已进入的上下文会自动退出 - 用
stack.callback(func)注册普通清理函数(不带异常参数) - 用
stack.push(func)注册带(exc_type, exc_value, traceback)参数的清理函数 - 所有注册操作必须在 yield 之前完成;yield 后无需管退出顺序——
ExitStack自动倒序执行
from contextlib import ExitStack <p>def open_multiple_files(filenames): with ExitStack() as stack: files = [stack.enter_context(open(f)) for f in filenames] yield files </p>
注意 @contextmanager 的生成器状态不可重入
同一个 @contextmanager 装饰的函数返回的对象,只能被一个 with 语句消费。重复使用(比如传给多个线程、或在循环里反复调用同一个实例)会触发 RuntimeError: generator didn't yield 或静默失效。
实操建议:
- 每次
with都应调用函数本身(如with db_transaction(conn):),而不是复用返回值 - 不要把
@contextmanager函数返回的对象存为实例变量或全局变量 - 若需跨作用域复用逻辑,封装成工厂函数,而非缓存生成器对象
最容易被忽略的是:生成器函数本身是无状态的,但它的每次调用产生的生成器实例是单次消费的。这和类实例的可重入性直觉相反,调试时容易卡在这里。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











