必须同时实现 enter 和 exit 方法,因 with 语句严格检查二者缺一不可;仅定义 enter 会报 AttributeError: exit__;__exit 须接收三个异常参数并按需返回 True/False 控制异常传播;@contextmanager 要求单次 yield 且需 try/finally 保证清理。

必须实现 __enter__ 和 __exit__ 两个方法,缺一不可;否则 with 语句会抛出 AttributeError: __enter__ 或类似错误。
为什么只加 __enter__ 不行?
Python 解释器在遇到 with 语句时,会**严格检查对象是否同时具备这两个方法**。即使你只关心“进入时初始化”,不写 __exit__ 也会直接报错——它不是可选的“清理钩子”,而是协议的强制组成部分。
常见错误现象:
- 定义了
__enter__但忘了写__exit__→ 报AttributeError: __exit__ - 把
__exit__写成__leave__或拼错名 → 同样报__exit__缺失 - 方法定义在父类但被子类覆盖且没调用
super().__exit__→ 子类实例实际无有效__exit__
__exit__ 的三个参数和返回值怎么设?
__exit__(self, exc_type, exc_val, exc_tb) 的设计目标是:无论代码块是否异常,都确保资源释放;同时允许你选择是否吞掉异常。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 若只需保证清理(最常见),
__exit__末尾加return None或直接不写return(等价于return False)→ 异常照常抛出 - 若想静默忽略特定异常(如
IOError),在内部判断exc_type is IOError后return True - 切勿在
__exit__中裸写except:或raise—— 它本就是异常处理入口,重复捕获易掩盖问题 - 参数为
(None, None, None)表示无异常;只要任一非None,说明发生了异常
用 @contextmanager 装饰器快速实现,但要注意 yield 规则
比起手写类,contextlib.contextmanager 是更轻量的选择,但它依赖生成器协议,对结构敏感。
关键约束:
- 被装饰函数必须是
generator(含yield) -
yield**只能出现一次**,且必须产出你要绑定给as变量的值 -
yield前的代码 =__enter__逻辑,yield后的finally块 =__exit__逻辑 - 若
yield后没有finally,资源不会被释放 → 必须用try/finally包裹清理动作
示例片段:
from contextlib import contextmanager <p>@contextmanager def db_connection(): conn = acquire_db() # <strong>enter</strong> 部分 try: yield conn # 绑定给 as 后的变量 finally: conn.close() # <strong>exit</strong> 部分,必执行
自定义类 vs @contextmanager:性能与复用差异
两者语义等价,但底层开销和适用场景不同。
选择依据:
- 需要保存状态(如重试次数、缓存句柄)→ 用类,方便在
self上存属性 - 逻辑简单、一次性使用(如临时切换目录、打日志标记)→
@contextmanager更紧凑 - 高频调用(如每毫秒创建一个上下文)→ 类实例略快(无生成器状态机开销),但差异通常可忽略
- 要支持装饰器用法(如
@my_context修饰函数)→ 必须继承contextlib.ContextDecorator,此时类更直接
容易被忽略的一点:__enter__ 的返回值决定 as 变量内容,而 @contextmanager 中 yield 的值就是它——如果 yield 后多加了个 print,不影响返回值;但如果 yield 写成 yield x; yield y,会直接报 RuntimeError: generator didn't yield。


















