@contextlib.contextmanager 装饰器将生成器函数自动转为上下文管理器:yield前为__enter__,yield值作as接收对象,yield后(含try/finally)等效__exit__;无需写类因其内部封装了协议实现。

直接用 @contextlib.contextmanager 装饰器是最轻量、最不易出错的方式,不用写类、不用实现 __enter__ 和 __exit__,但必须确保 yield 前后逻辑对称、异常传播路径清晰。
为什么不用写类?——@contextlib.contextmanager 的本质
它把一个生成器函数“翻译”成上下文管理器:yield 之前的代码当 __enter__ 执行,yield 的值就是 as 接收的对象,yield 之后的代码(包括 except / finally 块)等效于 __exit__ 的清理逻辑。
常见错误是把资源获取写在 yield 后面,或漏掉 try/finally 导致异常时资源未释放:
- yield 必须出现且只能出现一次
- yield 前抛异常 → 不进 yield,
__enter__失败,不执行__exit__ - yield 后(即
with块内)抛异常 → 异常会重新抛入生成器,在 yield 处作为except捕获,或由finally收尾
典型场景:临时修改状态并自动还原
比如临时切换日志级别、修改环境变量、切换工作目录。这类操作不需要复杂资源对象,纯靠状态变更 + 还原即可。
立即学习“Python免费学习笔记(深入)”;
示例:临时设置 os.environ 并确保恢复:
@contextlib.contextmanager
def temp_env(**updates):
old = {k: os.environ.get(k) for k in updates}
os.environ.update(updates)
try:
yield
finally:
for k, v in old.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
使用时:
with temp_env(FOO="bar", DEBUG="1"):
print(os.environ["FOO"]) # "bar"
print(os.environ.get("FOO")) # None(已还原)
注意:finally 是必须的,否则 with 块内 return 或异常都会跳过还原逻辑。
容易踩的坑:异常吞没、yield 值类型错乱、嵌套失效
三个高频问题:
- 在 yield 后用
except但没raise或raise e→ 吞掉原始异常,调试困难;正确做法是except BaseException as e: ...; raise - yield 一个非 None 值(如
yield conn),但调用方没写as conn→ 不报错但值被丢弃,容易误以为没生效 - 装饰器函数里用了闭包变量,又在多线程中使用 → 变量共享导致状态错乱;应避免在生成器内依赖外部可变状态
如果上下文需要支持异步(async with),@contextlib.contextmanager 不适用,得用 @contextlib.asynccontextmanager,且函数必须是 async def。
真正难的不是写出来,而是想清楚 yield 哪儿该放、异常到底在哪儿被捕获、还原逻辑是否覆盖所有退出路径——这些细节不画执行流图很容易漏。


















