python
#coding=utf-8
class Cooker: # 厨师
def open_gas(self):
print("正在打开天然气")
def close_gas(self):
print("正在关闭天然气")
def doworks(self):
print("正在制作小甜饼")
def __enter__(self):
self.open_gas()
return self # 此对象将被 as 绑定
def __exit__(self, exc_type, exc_value, exc_tb):
self.close_gas()
if exc_type is None:
print("with语句正常退出")
else:
print("with语句异常退出,", exc_value)
with Cooker() as c:
c.doworks() # 工作中
3/0 # ZeroDivisionError
c.doworks()
# 正常处理流程
# c = Cooker()
# c.open_gas()
# c.doworks()
# 3/0
# c.doworks()
# c.close_gas()
这是一个上下文管理器 (context manager)的示例:
把"开燃气→做活→关燃气"这种成对资源管理 封装进 with 语句里,不管中途是正常结束 还是出异常 ,都能确保最后关掉燃气。
现实对照:
-
open_gas():打开资源(文件/网络连接/锁/数据库事务...) -
doworks():执行任务 -
close_gas():释放资源(关闭/解锁/提交或回滚...)
with 语句会调用:
-
进入时:
__enter__ -
退出时:
__exit__(无论有没有异常都会调用)
执行流程图
A) 正常情况(无异常)
python
with Cooker() as c:
c.doworks()
流程:
__enter__ → open_gas
↓
块内执行 → doworks
↓
__exit__ → close_gas(exc_type 等参数都是 None)
B) 异常情况(比如块内 3/0)
python
with Cooker() as c:
c.doworks()
3/0
流程:
__enter__ → open_gas
↓
块内执行 → ZeroDivisionError 抛出
↓
__exit__ → close_gas(收到异常信息 exc_type/ exc_value/ exc_tb)
↓
__exit__ 返回值决定是否"吃掉异常":
- 返回 True → 异常被抑制,不再往外抛
- 返回 False/None → 异常继续向外抛
用 try/finally 等价写法
下面两段是效果等价的:
写法 1:with(推荐)
python
with Cooker() as c:
c.doworks()
3/0
c.doworks()
写法 2:手写 try/finally
python
c = Cooker()
c.open_gas()
try:
c.doworks()
3/0
c.doworks()
finally:
c.close_gas() # 无论是否异常,最终都会执行
差别:with 更简洁、不会忘记关资源,还能把异常信息交给 __exit__ 决定是否抑制。
控制异常
python
def __exit__(self, exc_type, exc_value, exc_tb):
self.close_gas() # 无论正常/异常退出,都先把资源关了
if exc_type is None:
print("with 语句正常退出")
else:
print("with 语句异常退出:", exc_value)
关键点:是否抑制异常?
若 return True ,异常被"吃掉",外面感觉不到
若 return False/None(当前代码等价于 None),异常会继续向外抛
这里没写 return,相当于返回 None → 不抑制异常