with 语句是 Python 中的上下文管理协议的语法糖,它的核心作用是:确保资源在使用完毕后能够被正确释放,无论过程中是否发生异常。
你平时最常用的场景就是文件操作:
with open('data.txt', 'r') as f:
content = f.read()
# 离开 with 块后,文件自动关闭,不需要调用 f.close()
其底层依赖的是一个约定好的协议:如果一个对象实现了 enter 和 exit 两个魔术方法,那么它就是“上下文管理器”,可以直接用在 with 语句中。
enter:进入with块时被调用,负责获取资源并返回一个对象(通常就是自身),这个对象可以用as子句绑定到一个变量上。exit:离开with块时被调用,负责释放资源。无论块内代码是正常结束还是抛出异常,exit都会执行。它接收三个参数:异常类型、异常值、异常追溯,如果返回True,则会吞掉异常,让程序继续执行;如果返回False或None,异常会继续向上传播。
执行流程
我们用一个自定义的上下文管理器来拆解具体的执行过程:
class MyContext:
def __enter__(self):
print("1. 进入上下文,获取资源")
return self # 返回的对象会赋给 as 后面的变量
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f"3. 发生异常:{exc_type.__name__},但依然会释放资源")
else:
print("3. 正常结束,释放资源")
return False # 不吞掉异常,继续向外抛出
with MyContext() as ctx:
print("2. 执行业务逻辑")
# raise ValueError("测试异常") # 可取消注释测试异常场景
执行顺序如下:
- 计算表达式:
MyContext()创建一个上下文管理器对象。 - 调用
enter:解释器自动调用该对象的enter方法,进入上下文。方法的返回值通过as绑定给变量ctx。 - 执行块内代码:顺序执行
with代码块中的业务逻辑。 - 离开块时调用
exit:
- 如果没有异常,
exc_type、exc_val、exc_tb均为None,正常释放资源。 - 如果块内代码抛出了异常,Python 会暂停块内执行,将异常信息传给
exit的三个参数,然后执行释放资源的代码。之后根据返回值决定是否继续抛出异常。
- 清理完毕:无论何种情况,
exit都会被调用,资源一定被释放。
这种设计的好处是:将“资源获取”和“资源释放”强绑定在一起,开发者不必再担心忘记关闭文件、释放锁、断开数据库连接等问题。使用 with 比手动写 try ... finally 更简洁、更安全,也更符合 Python 的优雅哲学。