使用@contextlib.contextmanager需确保yield前后分别对应__enter__和__exit__逻辑:yield前初始化并返回资源,yield后必须用try/finally做清理;yield仅一次且不可return;异常处理需用except捕获并显式re-raise;不支持异步,异步场景须用asynccontextmanager。

直接用 @contextlib.contextmanager 装饰器写生成器函数,就能快速实现上下文管理器,但 yield 前后逻辑必须严格对应 __enter__ 和 __exit__ 的职责,否则资源会泄漏或异常被吞。
yield 之前是 enter 逻辑,之后是 exit 逻辑
装饰器把生成器拆成两段:yield 左侧执行完就返回给 with 语句的 as 变量;yield 右侧(包括 finally 块)负责清理。不能在 yield 后面写普通 return,也不能漏掉异常处理。
- yield 必须出现且只能出现一次,否则会报
RuntimeError: generator didn't yield - 如果需要返回值给
as,yield 后面跟表达式,比如yield resource - 清理逻辑要包在
try/finally里,否则遇到异常时不会执行 ——@contextmanager不自动加 finally
from contextlib import contextmanager <p>@contextmanager def open_file(path): f = None try: f = open(path, 'r') yield f # ← 这个值赋给 as 后面的变量 finally: if f is not None: f.close() # ← 必须放 finally,否则异常时跳过 </p>
捕获并处理异常要用 except,不是 if
如果想在退出时根据是否发生异常做不同处理(比如只在出错时记录日志),得用 except 捕获,再重新 raise,不能靠判断某个变量是否为 None。
-
__exit__接收三个参数:exc_type, exc_value, traceback,@contextmanager把它们传进生成器的 except 块 - 若在 except 中 return True,异常会被吞掉;return None 或 False 则继续传播
- 不要试图在 yield 后检查
sys.exc_info(),此时异常上下文已丢失
@contextmanager
def log_on_failure():
try:
yield
except Exception as e:
print(f"Failed with {type(e).__name__}")
raise # ← 显式 re-raise,否则异常消失
不支持异步,别和 async/await 混用
@contextmanager 生成的是同步上下文管理器,套在 async with 里会直接报 TypeError: object __aenter__ is not callable。
- 异步场景必须用
@contextlib.asynccontextmanager(Python 3.7+) - 即使生成器函数本身用了
await,只要没用 asynccontextmanager,就不是合法的异步上下文管理器 - 同步装饰器无法识别
async def,会抛TypeError: cannot decorate asynchronous function
最常被忽略的是 finally 的必要性——很多人只写 try/yield,以为 yield 后的代码总会执行,其实不是。异常路径下,没有 finally 就等于没清理。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











