上下文管理器存在的目的是简化 try-finally 结构,即使代码在运行过程中出现了异常,也能正确释放对应的资源或执行特定的清理工作。
通过 with 语法使用上下文管理器,例如打开对应文件,在完成读写操作后关闭文件:
with open('test.txt') as fp:
src = fp.read(60)
上下文管理器对象实现了两个的方法:
- enter 方法,with 语句开始运行后会调用 enter 方法,将返回结果绑定到 as 后的目标变量
- exit 方法,在 with 块内程序执行完成后,会调用 exit 方法
上述例子中 open('test.txt') 语句返回的 TextIOWrapper 对象继承自 IOBase 基类,在该基类中实现了上下文管理器协议。
class IOBase(metaclass=abc.ABCMeta):
def __enter__(self): # That's a forward reference
"""Context management protocol. Returns self (an instance of IOBase)."""
self._checkClosed()
return self
def __exit__(self, *args):
"""Context management protocol. Calls close()"""
self.close()
简易实现
原始的上下文管理器实现方式较为繁琐,需要定义一个类并实现对应的协议,而使用 @contextmanager 注解能将一个生成器函数转换成上下文管理器。
如下例子展示了一个通过生成器实现的上下文管理器:
- 在 with 上下文中,print() 的输出内容会完全倒置 ,with 块运行结束后恢复
- 在 yield 暂定前执行的逻辑可以对应到上下文管理器中的 enter 方法,替换了原始的 sys.stdout.write,使其产生输出导致的效果
- 在 yield 暂定后执行的逻辑可以对应到上下文管理器中的 exit 方法,恢复为原始的 sys.stdout.write
- 需要特别注意:如果 with 块中抛出异常,该异常会在 yield 处再次抛出,如果没有处理异常则会导致 exit 逻辑永远不会执行
import contextlib import sys @contextlib.contextmanager def looking_glass(): original_write = sys.stdout.write def reverse_write(text): original_write(text[::-1]) sys.stdout.write = reverse_write try: yield #注意 需要处理异常情况 finally: sys.stdout.write = original_write with looking_glass(): print('Alice, Kitty and Snowdrop') print('Alice, Kitty and Snowdrop')
输出结果:
pordwonS dna yttiK ,ecilA
Alice, Kitty and Snowdrop
这种实现方式把 Python 中三个不同的功能组合在一起:装饰器,生成器,上下文管理器,确实是非常巧妙!