__enter__ 和 __exit__ 必须成对实现,因 python 在进入 with 块时强制调用 __exit__ 以保障资源自动释放;仅定义 __enter__ 会导致资源泄漏,且 __exit__ 必须接收三个参数并谨慎返回值。

为什么 __enter__ 和 __exit__ 必须成对实现
单独定义 __enter__ 没有意义,Python 解释器在进入 with 块时会强制调用 __exit__ —— 无论 with 块内是否抛出异常、是否提前 return、甚至是否执行到末尾。这是资源自动释放的底层保障机制。
常见错误是只实现 __enter__ 并在其中打开文件或连接,却忘了写 __exit__,结果资源永远不释放。必须确保 __exit__ 接收三个参数:exc_type、exc_value、traceback,哪怕你只写 pass 也得占位。
-
__exit__返回True会吞掉异常(慎用,仅限你明确要压制该异常) - 返回
None或False表示不压制,异常继续向上冒泡 - 即使
__exit__本身抛出新异常,原异常也会被丢弃(Python 规则),所以清理逻辑里尽量避免再抛错
用 contextlib.contextmanager 写轻量级上下文管理器
不需要每次都写类。当逻辑简单(比如临时修改环境变量、切换工作目录、开关日志级别),用装饰器方式更直接。
关键点:函数体内必须有且仅有一个 yield;yield 之前的代码相当于 __enter__,之后的相当于 __exit__(支持 try/finally)。
from contextlib import contextmanager
<p>@contextmanager
def temp_env(**kwargs):
old = {k: os.environ.get(k) for k in kwargs}
os.environ.update(kwargs)
try:
yield
finally:
for k in kwargs:
if old[k] is None:
os.environ.pop(k, None)
else:
os.environ[k] = old[k]
</p>
- 如果
yield后面没写finally,异常发生时清理逻辑不会执行 -
yield可以带值(如yield resource),这样with temp_env() as x:中的x就能接收到 - 不要在
yield后面写普通语句——它们只在正常退出时运行,异常退出时会被跳过
with 嵌套多个资源时的异常传播顺序
写成 with a(), b(): 等价于嵌套 with a(): with b():,但退出顺序是反的:先执行 b.__exit__,再执行 a.__exit__。这对错误恢复很关键。
例如数据库连接 a 和事务 b:事务必须先回滚/提交(b.__exit__),连接才能安全关闭(a.__exit__)。如果顺序错了,可能在事务未结束时就断开连接,导致状态不一致。
- 多个资源用逗号分隔时,任一
__enter__抛异常,已成功__enter__的资源仍会按反序调用__exit__ - 但如果第一个
__enter__就失败,后面的根本不会执行,自然也没有对应__exit__ - 不推荐手动嵌套多层
with,易读性差;优先用逗号语法,除非需要差异化错误处理
自定义上下文管理器中忽略特定异常的陷阱
有时你想让 __exit__ 忽略某个清理错误(比如删除临时文件时发现已被删),但又不想影响主流程异常。这时候不能简单 except OSError: pass —— 因为 __exit__ 本身不该抛异常,否则会覆盖原始异常。
正确做法是在清理逻辑里捕获并吞掉无关异常,同时确保返回值不干扰主异常传播:
def __exit__(self, exc_type, exc_value, traceback):
try:
self.file.close()
except AttributeError:
pass # file was never opened
try:
os.unlink(self.temp_path)
except FileNotFoundError:
pass # already gone, fine
# 不 return True!除非你真想压制主异常
return False
- 返回
True是全局压制,不是“只压制清理异常”——它会让整个with块看起来像没出错 - 清理阶段的异常必须内部消化,绝不能让它逃出
__exit__ - 如果清理失败意味着资源处于危险状态(如锁没释放),那就别吞,而是记录警告或重新抛一个更明确的异常
真正难处理的是跨线程或异步资源,with 本身不提供线程安全保证,这类场景得额外加锁或改用 async with。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











