@contextmanager比手动实现__enter__/__exit__更简洁安全,自动保障清理逻辑执行、异常正确传播;带参管理器需外层函数接收参数并返回被装饰的无参内部函数,且yield只能出现一次。

直接用 @contextmanager 装饰器写,比手写 __enter__/__exit__ 简洁得多,且出错时异常传播更自然。
为什么不用手动实现 __enter__ 和 __exit__?
手动实现容易漏掉异常处理逻辑,比如忘记在 __exit__ 中返回 True 来吞掉异常,或误判异常类型导致本该抛出的错误被静默吞掉。更常见的是资源清理代码(如 close())没放在 finally 块里,遇到异常就跳过清理。
-
@contextmanager把函数体自动拆成进入、退出两段,中间 yield 之前是__enter__,之后是__exit__的清理逻辑 - yield 后的代码天然运行在
finally语义下,无论是否异常都会执行 - 若 yield 处抛出异常,会原样传给
__exit__参数,无需手动捕获再 re-raise
怎么写一个带参数的上下文管理器?
把装饰器套在接收参数的函数外层,返回一个新的上下文管理器工厂函数。注意:不能直接对参数化函数加 @contextmanager,否则每次调用都复用同一个生成器实例。
from contextlib import contextmanager
<p>def timer(name):
@contextmanager
def _timer():
import time
start = time.time()
try:
yield
finally:
print(f"{name} took {time.time() - start:.2f}s")
return _timer()</p><h1>使用</h1><p>with timer("db-query") as t:
time.sleep(1)
</p>
- 外部函数
timer()接收参数并返回一个被@contextmanager包装的内部函数 - 内部函数必须是无参的,且只 yield 一次(yield 值可被
as捕获) - 如果想让上下文管理器返回值(如文件对象),yield 后面跟上值:
yield resource
__exit__ 返回 True 会吞掉异常,但 @contextmanager 不鼓励这么做
用 @contextmanager 时,若要在 yield 后“吞掉”某个异常,必须显式检查 sys.exc_info(),而不是靠返回 True —— 因为装饰器生成的 __exit__ 默认不返回值,即等价于返回 None(也就是 False)。
- 想吞异常?得在 yield 后加
except块,并确保不 re-raise - 常见错误:在 yield 后写
return True—— 这会让整个生成器提前结束,清理逻辑不执行 - 真正需要吞异常的场景(如忽略
FileNotFoundError),建议用传统类方式,逻辑更可控
最易被忽略的是:yield 只能出现一次,且不能出现在嵌套作用域(如闭包函数里)。一旦 yield 多次或位置不对,@contextmanager 会报 RuntimeError: generator didn't yield 或类似错误 —— 这类问题不会在定义时暴露,只在 with 语句执行时触发。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











