直接写 enter 和 exit 容易出错,因 exit 返回值影响异常传播(true 吞异常),且 enter 失败后 exit 仍执行可能导致空指针访问;推荐优先用 @contextmanager 或 exitstack。

为什么直接写 __enter__ 和 __exit__ 很容易出错?
手写类实现上下文管理器时,__exit__ 的返回值逻辑最容易被忽略:如果它返回 True,异常会被吞掉;返回 None 或 False 才会继续抛出。但很多人只关注资源获取和释放,忘了处理异常传播,导致调试时发现错误“消失”了。
另一个常见问题是资源未正确清理——比如在 __enter__ 中出错后,__exit__ 仍被调用,但此时资源可能根本没初始化成功,直接操作就会触发 AttributeError。
所以除非你明确需要细粒度控制异常行为或复用状态,否则优先用 contextlib.contextmanager 装饰器,它自动帮你隔离异常、避免空指针访问。
用 @contextmanager 写一个带参数的临时目录管理器
contextlib.contextmanager 把函数变成上下文管理器,关键在于用 yield 分隔进入和退出逻辑。yield 前是 __enter__,之后是 __exit__(无论是否异常都会执行)。
from contextlib import contextmanager import tempfile import shutil <p>@contextmanager def temp_dir(suffix="", prefix="tmp"): path = tempfile.mkdtemp(suffix=suffix, prefix=prefix) try: yield path finally: shutil.rmtree(path, ignore_errors=True) </p>
使用时:
with temp_dir(suffix=".data") as d: # 在 d 目录里写文件 open(f"{d}/test.txt", "w").write("ok")
注意:try/finally 是必须的——不能只用 yield 后直接写 shutil.rmtree,否则 __enter__ 抛异常时 rmtree 会收到未创建的路径。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
contextlib.ExitStack 适合哪些场景?
当你需要动态管理多个上下文(数量不确定、类型不统一),或者想在运行时决定是否进入某个上下文,ExitStack 比嵌套 with 更灵活。
常见用途:
- 打开一组文件,但其中某些可能不存在,跳过报错的
- 为测试临时 patch 多个模块对象,且需保证全部还原
- 在函数中根据条件决定是否启用日志捕获或 mock
示例:安全地打开多个可选配置文件
from contextlib import ExitStack <p>config_paths = ["/etc/app.conf", "/usr/local/app.conf", "./local.conf"] configs = [] with ExitStack() as stack: for path in config_paths: try: f = stack.enter_context(open(path)) configs.append(f.read()) except FileNotFoundError: continue # 忽略缺失文件,不中断整个栈 </p>
这里 stack.enter_context() 会自动注册 close() 或 __exit__,即使中间某次 open() 失败,前面已打开的文件仍会被关闭。
自定义类 + contextlib.closing 的坑在哪?
contextlib.closing 只调用对象的 close() 方法,它不检查该方法是否存在或是否幂等。如果你传入一个没有 close() 的对象,会立刻报 AttributeError;如果 close() 不是幂等的(比如第二次调用会报错),在异常路径下可能重复触发。
更隐蔽的问题是:它无法替代真正需要 __enter__ 初始化逻辑的场景。例如数据库连接通常要先 connect() 再 close(),而 closing 只负责收尾。
所以除非你确认对象满足:
- 有 close() 且幂等
- 不需要 __enter__ 阶段的额外准备
否则别图省事硬套 closing,老实用 @contextmanager 或写完整类。
真正难的不是语法,是判断哪里该用装饰器、哪里该用 ExitStack、哪里必须自己写类——边界常出现在异常传播策略和资源依赖关系上。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










