文件、数据库连接、锁和临时目录都有共同点:获取资源后,无论中途是否抛出异常,都必须执行清理。Python 的 with 语句把这个约束写进了代码结构里。
最常见的例子是文件:
with open("config.json", encoding="utf-8") as file:
content = file.read()
离开代码块时,文件会自动关闭。自定义资源也可以获得同样的行为。对于简单场景,contextlib.contextmanager 比手写类更轻巧:
from contextlib import contextmanager
import sqlite3
@contextmanager
def open_database(path: str):
connection = sqlite3.connect(path)
try:
yield connection
connection.commit()
except Exception:
connection.rollback()
raise
finally:
connection.close()
使用时,调用方只关心业务操作:
with open_database("app.db") as db:
db.execute(
"insert into events(name) values (?)",
("user_signed_in",),
)
yield 之前相当于进入上下文,yield 之后相当于退出上下文。异常会在 yield 所在位置重新出现,因此可以统一回滚,再用裸 raise 保留原始异常栈。
一个常见错误是在清理逻辑中吞掉异常。除非确实要把异常转换成可接受结果,否则应该继续抛出,让调用方知道操作没有成功。
当同一段资源清理代码出现两次时,就值得考虑上下文管理器。它不仅减少重复,更重要的是让“申请—使用—释放”成为一个不可拆散的整体。