__enter__ 和 __exit__ 必须成对出现,因为 with 语句强制调用二者;缺一即报 AttributeError 或 TypeError,且 __exit__ 必须接收三个参数并正确处理异常与清理逻辑。

为什么 __enter__ 和 __exit__ 必须成对出现
因为 Python 的 with 语句在进入时强制调用 __enter__,退出时(无论是否异常)强制调用 __exit__;少一个,with 就直接报 AttributeError: __exit__ 或类似错误。
常见错误现象:with MyContext(): 报错 TypeError: 'MyContext' object is not a context manager,基本就是漏了 __exit__,或者拼错了名字(比如写成 __exxit__)。
-
__enter__返回值会绑定到as后的变量,不返回(即隐式返回None)也合法,但多数场景要返回资源本身或关键对象 -
__exit__必须接收 3 个参数:exc_type、exc_value、traceback;哪怕全用_忽略,也不能少参数,否则调用时崩溃 - 如果
__exit__返回True,会吞掉异常;返回False或None才继续抛出——这是控制异常传播的关键开关
怎么让 __exit__ 正确处理异常和清理逻辑
别在 __exit__ 里手动 raise 原异常,Python 已经帮你做了;重点是:先做清理,再决定是否压制异常。
典型使用场景:文件打开失败、网络连接中断、临时目录未删干净——这些都要在 __exit__ 里兜底。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 清理操作(如关闭句柄、删除临时文件)必须放在
__exit__开头,避免因异常提前退出导致资源泄漏 - 判断是否有异常:用
if exc_type is not None:,而不是if exc_value:,因为有些异常的exc_value可能为 falsy(比如Exception("")) - 不要在
__exit__里写复杂业务逻辑,它只该做“善后”;若需重试或转换异常,请在with块内处理
def __exit__(self, exc_type, exc_value, traceback):
self._cleanup() # 先清理
if exc_type is not None:
log_error(f"Caught {exc_type.__name__}: {exc_value}")
return False # 不压制异常自定义上下文管理器 vs contextlib.contextmanager 怎么选
写类实现 __enter__/__exit__ 更适合需要保存状态、复用实例、或配合继承的场景;而 @contextmanager 更轻量,适合一次性的、逻辑简单的资源管理。
性能差异不大,但兼容性有坑:装饰器方式生成的上下文管理器,在某些老版本 Python(如 3.6 之前)中可能不支持 __aenter__/__aexit__,而手写类更容易显式补全异步支持。
- 如果类里已有大量属性和方法,加两个魔术方法成本低,且语义清晰
- 如果只是封装一个
open()或requests.session(),用@contextmanager写三行更直白 -
@contextmanager函数里不能用return提前退出,否则上下文无法正常结束;必须用yield,且yield后代码才是__exit__阶段
容易被忽略的细节:返回值类型和 None 检查
__enter__ 的返回值会直接赋给 as 变量,如果它返回 None,而你又写了 as f,那 f 就是 None——后续调用 f.read() 就崩了,但错误位置离定义很远,难定位。
常见错误现象:AttributeError: 'NoneType' object has no attribute 'write',实际是 __enter__ 忘了 return self 或返回了错误对象。
- 务必检查
__enter__是否有明确return,尤其在条件分支多的时候 - 如果返回的是新对象(比如返回一个封装后的
io.StringIO),确保它的接口和预期一致 - 单元测试里一定要覆盖
with ... as x:后对x的使用,避免空指针式错误潜入生产环境
事情说清了就结束

















