结论:写上下文管理器优先用 @contextmanager 装饰器把生成器函数转成管理器(yield 前是 __enter__、后是 __exit__),比手写类协议简洁;模块还备齐了一批高频工具:closing(给只有 close 的对象补协议)、suppress(静默指定异常)、ExitStack(动态数量资源的统一退场)、redirect_stdoutnullcontext(占位管理器)。

展开:@contextmanager 的正确姿势要记住异常流向:with 块内抛出的异常会在 yield 处重新抛入生成器,想处理就 try/finally 包住 yield——这等价于类版 __exit__ 返回 False 不抑制、返回 True 抑制。ExitStack 解决"资源数量运行时才确定"的问题(打开 N 个文件、注册 N 个回调),按注册逆序统一清理,是手写嵌套 with 的通用替代。

from contextlib import contextmanager, ExitStack

@contextmanager
def timer(label):
    import time
    t0 = time.perf_counter()
    try:
        yield                       # with 块在这里执行
    finally:                        # 无论是否异常都执行
        print(f"{label}: {time.perf_counter() - t0:.3f}s")

with ExitStack() as stack:
    files = [stack.enter_context(open(p)) for p in paths]  # 统一关闭

易错点:生成器版管理器不能重入(同一个实例嵌套进入会报错),需要可重入用类实现;suppress 只该包住预期异常的最小代码块。追问方向:__exit__ 的三个参数(异常类型/值/traceback)语义、异步上下文管理器 async withasynccontextmanager