2.24. 上下文管理器¶
with 语句会先运行设置代码,然后运行主体,再运行清理代码——并保证即使主体在中途失败,清理代码也会运行。提供设置和清理的那一对方法称为上下文管理器。
其形式为:
with <expression> as <name>:
<body>
该表达式返回一个上下文管理器。Python 调用它的 __enter__ 方法,可选地将结果绑定到 <name>,运行主体,然后调用 __exit__ ——无论主体是正常完成还是抛出了异常。
无论代码块内部发生什么,__exit__ 都会在块的末尾运行。¶
2.24.1. 使用上下文管理器¶
最典型的例子是打开文件:
with open("data.txt") as f:
text = f.read()
# f is now closed, even if read() failed
open() 返回一个文件对象,它本身就是一个上下文管理器;__enter__ 返回该文件,__exit__ 关闭它。with 块让"用完后总是关闭文件"成为默认行为,而不是某件调用者必须记住的事。
2.24.1.1. 多个上下文管理器¶
单条 with 语句可以一次进入多个上下文管理器,以逗号分隔:
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
这等价于两个嵌套的 with 块,但更扁平。这些管理器按从左到右的顺序进入,并以相反顺序退出;如果右侧管理器的 __enter__ 抛出异常,左侧管理器的 __exit__ 仍会运行。
2.24.2. 编写上下文管理器¶
任何带有 __enter__ 和 __exit__ 方法的类都可以作为上下文管理器:
class Section:
def __init__(self, label):
self.label = label
def __enter__(self):
print("---", self.label, "---")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("--- end", self.label, "---")
return False
with Section("setup"):
print("doing the work")
输出::
--- setup ---
doing the work
--- end setup ---
__exit__ 接收三个描述结束该块的异常的参数,如果块正常完成则接收三个 None 值。返回 False(或 None)会让任何异常在清理之后继续传播;返回 True 则会吞掉该异常。
对于任何具有"打开/关闭"或"获取/释放"生命周期的资源,都应使用上下文管理器——而不仅仅是文件。这种模式让清理与设置在二者被引入的同一处成对出现,因此在一个长函数中间忘记某个关闭操作也不会泄漏资源。