用自定义__enter__/__exit__验证正常退出、内部抛异常、__exit__吞掉异常三种场景,看with语句是怎么保证资源释放的。
实现__enter__和__exit__,让每次调用都打印出来。
代码class Resource: def __enter__(self): print("__enter__ 被调用,资源已打开") return self def __exit__(self, exc_type, exc_val, exc_tb): print(f"__exit__ 被调用,exc_type={exc_type}") print("资源已释放(不管上面是否出异常)") return self.suppress
正常退出、内部抛异常、__exit__选择吞掉异常——三种的运行轨迹。
场景1:正常退出,输出[A] __enter__ 被调用,资源已打开 with块内部在执行 [A] __exit__ 被调用,exc_type=None [A] 资源已释放 场景2:内部抛出ValueError,输出[B] __enter__ 被调用,资源已打开 with块内部在执行 [B] __exit__ 被调用,exc_type=<class 'ValueError'> [B] 资源已释放 在with外面捕获到异常: 模拟一个错误 场景3:__exit__返回True,吞掉异常[C] __enter__ 被调用,资源已打开 with块内部在执行 [C] __exit__ 被调用,exc_type=<class 'RuntimeError'> [C] 资源已释放 程序走到这里,外面完全没有捕获到任何异常
三种场景里,__exit__ 都被调用了——不管with块内部是正常结束还是抛出了异常。这就是with语句提供的保证:资源清理代码总能执行到,不需要你手写try/finally。
__exit__ 如果返回 True,这个异常就会被吞掉,不会再往外传播——外层代码完全感知不到曾经发生过异常。这是个强大但也危险的能力:如果不是刻意要实现"忽略特定异常"的逻辑(比如 contextlib.suppress),不应该让 __exit__ 无脑返回True,否则会悄悄吞掉本该暴露出来的bug。
每一步都是本文上面用Python 3.14跑出来的原始输出。
__enter__ / __exit__:验证with语句的三种退出场景