(十五)用上下文管理器安排进入与退出
1. with、__enter__ 与 __exit__
程序使用文件、数据库连接等资源时,需要把“开始使用”和“结束处理”接起来。先用一个连接标志模拟这个过程,观察调用顺序:
# 这里用 opened 模拟连接状态,暂时不创建真实的外部连接。
class ReadingSession:
def __init__(self):
self.opened = False
def __enter__(self):
self.opened = True
print("进入")
return self
def __exit__(self, exc_type, exc, traceback):
self.opened = False
print("退出", "正常" if exc_type is None else exc_type.__name__)
return False
with ReadingSession() as session:
print(session.opened)
print(session.opened)输出“进入”、True、“退出 正常”、False。with 取得上下文管理器,先调用 __enter__,把其返回值交给 as 后的名称。本例返回 self,所以 session 指向这个 ReadingSession 实例;主体运行结束后再调用 __exit__。
正常退出时,__exit__ 收到的三个异常参数都是 None。它在这里把连接标志改回关闭状态。离开 with 不会删除 session 这个名称,最后一行仍能读取对象,只是资源已经完成退出处理。
2. 主体发生错误时先退出,再向外传递
# 主体抛出 RuntimeError,退出方法仍会处理状态,再由外层接收错误。
class ReadingSession:
def __init__(self):
self.opened = False
def __enter__(self):
self.opened = True
print("进入")
return self
def __exit__(self, exc_type, exc, traceback):
self.opened = False
print("退出", "正常" if exc_type is None else exc_type.__name__)
return False
try:
with ReadingSession() as session:
print("处理记录")
raise RuntimeError("读取失败")
except RuntimeError as error:
print("外层收到", error)
print(session.opened)输出“进入”“处理记录”“退出 RuntimeError”“外层收到 读取失败”,最后是 False。RuntimeError 是这里主动发出的运行错误;异常发生后,主体后续语句停止,__exit__ 接收异常类型、异常对象和追踪信息,再把状态关闭。
返回 False 使异常继续传给外层处理;返回真值则表示当前管理器接收并抑制了主体的异常。普通的资源关闭只负责完成清理,可以返回 False 或不显式返回;不要为了退出处理顺利显示,就把原本的错误一起隐藏。下一章会把这个顺序接到真实文件的读写和关闭上。with 语句