Python高级-上下文管理器

上下文管理器存在的目的是简化 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 中三个不同的功能组合在一起:装饰器,生成器,上下文管理器,确实是非常巧妙!