最简上下文管理器用@contextmanager装饰生成器函数,yield前为__enter__,后为__exit__;类方式适合需状态管理的场景;ExitStack用于动态组合多个上下文;closing()仅兼容无上下文协议但有close()方法的对象,Python 3.10+已弃用。

用 @contextmanager 装饰器写最简上下文管理器
不需要写类,@contextmanager 是最快捷的自定义方式。它把一个生成器函数变成上下文管理器,yield 之前的部分相当于 __enter__,之后是 __exit__。
常见错误是忘记 yield 或在 yield 后漏写异常处理逻辑——这会导致退出时异常被吞掉或资源未释放。
-
yield可以带返回值,这个值会成为with语句中as绑定的对象 - 如果
yield后代码抛出异常,它会被传递给调用方;若想拦截,需用try/except包裹yield后的逻辑 - 生成器函数里不能有多个
yield,否则会报RuntimeError: generator didn't yield
from contextlib import contextmanager <p>@contextmanager def open_log(path): f = open(path, 'a') try: yield f # f 成为 with open_log(...) as f 中的 f finally: f.close() # 即使出错也确保关闭
手动实现 __enter__ 和 __exit__ 类更可控
当需要保存状态、复用实例或做复杂资源调度时,类方式更合适。比如要支持嵌套使用、记录进入/退出次数、或根据异常类型做不同清理动作。
容易踩的坑是 __exit__ 返回值逻辑:仅当返回 True 才会抑制异常;返回 None 或 False 都会让异常继续向上冒泡。
立即学习“Python免费学习笔记(深入)”;
-
__exit__(self, exc_type, exc_value, traceback)四个参数必须全写,哪怕只用其中一两个 - 不要在
__exit__里直接raise新异常,除非明确要替换原异常;否则应返回False让原异常传播 - 如果
__enter__抛异常,__exit__不会被调用,所以初始化失败的清理逻辑得放在__init__或__enter__内部
contextlib.ExitStack 适合动态组合多个上下文
当你不知道要进几个上下文,或者要按条件添加资源(比如日志文件、数据库连接、临时目录),ExitStack 比硬写嵌套 with 更灵活。
典型误用是把 enter_context() 和 callback() 混用却不注意执行顺序:回调按注册**逆序**执行,而 enter_context() 的退出顺序与进入顺序相反。
-
enter_context(cm)用于管理已有上下文管理器,返回其__enter__结果 -
callback(func, *args, **kwds)注册普通函数,在退出时调用,不依赖__exit__ - 所有注册操作必须在
with stack:块内完成,否则会报RuntimeError: ExitStack is not entered
from contextlib import ExitStack <p>with ExitStack() as stack: files = [stack.enter_context(open(f)) for f in ['a.txt', 'b.txt']] stack.callback(print, 'all files closed')
为什么不用 contextlib.closing() 直接包装任意对象
closing() 只适合那些有 close() 方法但没实现上下文协议的对象,比如某些老库返回的 socket 或 response 对象。它本质就是个预设好的单方法上下文管理器。
别把它当万能胶——如果对象的 close() 不是幂等的,或调用后还可能被误用,那 closing() 反而掩盖问题;更糟的是,它对没有 close() 方法的对象会直接抛 AttributeError。
- 只适用于“调了
close()就完事”的场景,不支持定制清理逻辑 - Python 3.10+ 中已标记为
Deprecated,官方建议优先用@contextmanager或类实现 - 若对象有
shutdown()或cleanup()这类非标准方法,必须手写管理器,不能靠closing()
真正难的不是写出来,而是判断该用哪一种:简单一次性资源用 @contextmanager,需复用或状态管理用类,动态组合用 ExitStack,而 closing() 现在基本只是兼容旧代码的过渡方案。


















