with
语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文。 这么做能避免错误并减少样板代码,因此 API 更安全,而且更易于使用。除了自动关闭文 件之外,with
块还有很多用途。
上下文管理器对象存在的目的是管理 with
语句,就像迭代器的存在是为了管理 for
语句 一样。
with
语句的目的是简化 try/finally
模式。这种模式用于保证一段代码运行完毕后执行某 项操作,即便那段代码由于异常
、return
语句或 sys.exit()
调用而中止,也会执行指定的 操作。finally
子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。
上下文管理器协议包含 __enter__
和 __exit__
两个方法。with
语句开始运行时,会在上下 文管理器对象上调用 __enter__
方法。with
语句运行结束后,会在上下文管理器对象上调 用 __exit__
方法,以此扮演 finally
子句的角色。
最常见的例子是确保关闭文件对象。使用 with 语句关闭文件的详细说明参见如下示例:
解释:
1、f
绑定到打开的文件上,因为文件的 __enter__
方法返回 self
。
2、从 f
中读取一些数据。
3、f
变量仍然可用,可以读取 f 对象的属性。
4、但是不能在 f
上执行 I/O
操作,因为在 with
块的末尾,调用TextIOWrapper.__exit__
方法把文件关闭了。
执行 with
后面的表达式 得到的结果是上下文管理器对象,不过,把值绑定到目标变量上(as 子句)是在上下文管 理器对象上调用 __enter__
方法的结果。
上面的示例中的 open()
函数返回 TextIOWrapper
类的实例,而该实例的 __enter__
方 法返回 self
。不过,__enter__
方法除了返回上下文管理器之外,还可能返回其他对象。
不管控制流程以哪种方式退出 with
块,都会在上下文管理器对象上调用 __exit__
方法, 而不是在 __enter__
方法返回的对象上调用。
with
语句的 as
子句是可选的。对 open
函数来说,必须加上 as
子句,以便获取文件的引用。不过,有些上下文管理器会返回 None
,因为没什么有用的对象能提供给用户。
下面使用一个精心制作的上下文管理器执行操作,以此强调上下文管理器
与 __enter__
方法返回的对象之间的区别。
"""
上下文管理器
"""
class LookingGlass:
# 除了 self 之外,Python 调用 __enter__ 方法时不传入其他参数。
def __enter__(self):
import sys
# 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用
self.original_write = sys.stdout.write
# 为 sys.stdout.write 打猴子补丁,替换成自己编写的方法
sys.stdout.write = self.reverse_write
# 返回 'JABBERWOCKY' 字符串,这样才有内容存入目标变量 what
return 'JABBERWOCKY'
# 这是用于取代 sys.stdout.write 的方法,把 text 参数的内容反转,然后调用原来的实现。
def reverse_write(self, text):
self.original_write(text[::-1])
# 如果一切正常,Python 调用 __exit__ 方法时传入的参数是 None, None, None;
# 如果抛 出了异常,这三个参数是异常数据,如下所述。
def __exit__(self, exc_type, exc_value, traceback):
# 重复导入模块不会消耗很多资源,因为 Python 会缓存导入的模块。
import sys
# 还原成原来的 sys.stdout.write 方法。
sys.stdout.write = self.original_write
print('退出时,上下文管理器对象调用__exit__')
# 如果有异常,而且是 ZeroDivisionError 类型,打印一个消息……
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
# ……然后返回 True,告诉解释器,异常已经处理了
return True
# 如果 __exit__ 方法返回 None,或者 True 之外的值,
# with 块中的任何异常都会向上冒泡。
if __name__ == '__main__':
# 上下文管理器是 LookingGlass 类的实例;
# Python 在上下文管理器上调用 __enter__ 方 法,
# 把返回结果绑定到 what 上。
with LookingGlass() as what:
# 打印一个字符串,然后打印 what 变量的值。
# 打印出的内容是反向的。
print('Alice, Kitty and Snowdrop')
print(what)
# 现在,with 块已经执行完毕。
# 可以看出,__enter__ 方法返回的值——即存储在 what 变量中的值——是字符串 'JABBERWOCKY'
print(what)
# 输出不再是反向的了。
print('Back to normal.')
运行结果:
在实际使用中,如果应用程序接管了标准输出,可能会暂时把 sys.stdout
换 成类似文件的其他对象,然后再切换成原来的版本。contextlib.redirect_ stdout
上下文管理器(https://docs.python.org/3/library/contextlib.html#contextlib. redirect_stdout)
就是这么做的:只需传入类似文件的对象,用于替代sys. stdout
。
解释器调用 __enter__
方法时,除了隐式的 self
之外,不会传入任何参数。传给 __exit__
方法的三个参数列举如下:
# 异常类(例如 ZeroDivisionError)
exc_type
# 异常实例。有时会有参数传给异常构造方法,
# 例如错误消息,这些参数可以使用 exc_ value.args 获取。
exc_value
# traceback 对象
# 在 try/finally 语句的 finally 块中调用 sys.exc_info()
# (https://docs.python.org/3/library/sys.html#sys.exc_ info),
# 得到的就是 __exit__ 接收的这三个参数。
# 鉴于 with 语句是为了取代大多数 try/finally 语句,
# 而且通常需要调用 sys.exc_info() 来判断做什么清理操作,这种行为是合理的。
traceback
上下文管理器的具体工作方式参见如下示例。在这个示例中,我们在 with
块之外使用 LookingGlass
类,因此可以手动调用 __enter__
和 __exit__
方法。代码和调用如下:
"""
上下文管理器
"""
class LookingGlass:
# 除了 self 之外,Python 调用 __enter__ 方法时不传入其他参数。
def __enter__(self):
import sys
# 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用
self.original_write = sys.stdout.write
# 为 sys.stdout.write 打猴子补丁,替换成自己编写的方法
sys.stdout.write = self.reverse_write
# 返回 'JABBERWOCKY' 字符串,这样才有内容存入目标变量 what
return 'JABBERWOCKY'
# 这是用于取代 sys.stdout.write 的方法,把 text 参数的内容反转,然后调用原来的实现。
def reverse_write(self, text):
self.original_write(text[::-1])
# 如果一切正常,Python 调用 __exit__ 方法时传入的参数是 None, None, None;
# 如果抛 出了异常,这三个参数是异常数据,如下所述。
def __exit__(self, exc_type, exc_value, traceback):
# 重复导入模块不会消耗很多资源,因为 Python 会缓存导入的模块。
import sys
# 还原成原来的 sys.stdout.write 方法。
sys.stdout.write = self.original_write
print('退出时,上下文管理器对象调用__exit__')
print(sys.exc_info())
# 如果有异常,而且是 ZeroDivisionError 类型,打印一个消息……
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
# ……然后返回 True,告诉解释器,异常已经处理了
return True
# 如果 __exit__ 方法返回 None,或者 True 之外的值,
# with 块中的任何异常都会向上冒泡。
if __name__ == '__main__':
# 上下文管理器是 LookingGlass 类的实例;
# Python 在上下文管理器上调用 __enter__ 方 法,
# 把返回结果绑定到 what 上。
# with LookingGlass() as what:
# 打印一个字符串,然后打印 what 变量的值。
# 打印出的内容是反向的。
# print('Alice, Kitty and Snowdrop')
# print(what)
# 现在,with 块已经执行完毕。
# 可以看出,__enter__ 方法返回的值——即存储在 what 变量中的值——是字符串 'JABBERWOCKY'
# print(what)
# 输出不再是反向的了。
# print('Back to normal.')
# 实例化并审查 manager 实例
manager = LookingGlass()
print(manager)
# 在上下文管理器上调用 __enter__() 方法,把结果存储在 monster 中。
monster = manager.__enter__()
print(monster)
# monster 的值是字符串 'JABBERWOCKY'。
# 打印出的 True 标识符是反向的,
# 因为 stdout 的 所有输出都经过 __enter__ 方法中打补丁的 write 方法处理。
print(monster == 'JABBERWOCKY')
print(manager)
# 调用 manager.__exit__,还原成之前的 stdout.write
print(manager.__exit__(None, None, None))
print(monster)
运行结果: