核心作用:进入时做资源打开,退出时自动执行清理(shutdown/close),就算抛异常也一定会执行,不用手动写 try‑finally。
1. 两种写法
方式1:直接使用现成支持 with 的对象(日常最多)
很多库自带__enter__/__exit__,直接套with:
# 文件,退出自动closewithopen("test.txt","r",encoding="utf‑8")asf:data=f.read()# 数据库连接、线程锁、http session 都是同理离开 with 代码块,自动调用.close(),发生异常也会关闭。
方式2:自己实现类的上下文管理器(自定义,自动 shutdown)
实现两个魔术方法:
__enter__:进入with块时执行,返回值给as 变量__exit__:退出块时无论正常/异常都会执行,做 shutdown、释放资源
classMyClient:def__init__(self):passdefstart(self):"""启动连接"""print("启动连接")defshutdown(self):"""销毁、关闭资源"""print("执行 shutdown,释放连接")def__enter__(self):self.start()returnself# 给到 with ... as objdef__exit__(self,exc_type,exc_val,exc_tb):# exc_type 不为None代表里面抛出了异常self.shutdown()# 返回 True:吃掉异常;返回False/None:向外抛出异常returnFalse# 使用withMyClient()ascli:print("业务逻辑执行")# 哪怕这里抛异常,也会自动走到 shutdown# raise ValueError("出错啦")# 出with块,已经自动shutdown
__exit__的三个参数:异常类型、异常实例、堆栈。不需要处理异常直接忽略即可。
方式3:@contextmanager装饰器(写起来最简,工程代码最常用)
不用写类,用生成器函数,yield之前是进入逻辑,yield之后是退出/清理逻辑。
⚠️ yield 后面的代码,哪怕内部抛异常依然会执行,非常适合做 shutdown。
fromcontextlibimportcontextmanager@contextmanagerdefmy_resource():# ========== enter 阶段:打开资源 ==========print("打开资源")try:yield"资源对象"# 这里产出的值给到 as 后面变量finally:# ========== exit 阶段:一定会执行,shutdown/释放 ==========print("自动执行 shutdown,释放资源")# 调用withmy_resource()asres:print(f"使用:{res}")# raise Exception("模拟报错")# 出with,已经释放这是写工具、封装客户端最推荐的写法,代码短。
要点
with本质就是语法糖,等价手写try‑finally
obj=MyClient()obj.__enter__()try:# 业务代码finally:obj.__exit__(...)__exit__/ finally 一定会跑,但是不能保证进程被 os kill 的场景(系统强杀进程,Python 代码没机会执行清理)。- 大模型/向量库场景常见:把客户端、Milvus 连接、MinerU http session 包成上下文管理器,出块自动关闭连接。
极简对比总结
| 写法 | 适用场景 |
|---|---|
| 直接 with 对象 | 库已经实现好,日常读写文件、锁、session |
class +__enter__/__exit__ | 需要复杂类实例,要维护大量成员属性 |
@contextmanager | 轻量封装,只需要打开‑关闭逻辑,工程最常用 |