上下文管理器是为with 语句而生。只要实现了上下文管理器协议__enter__与__exit__,就可以使用with语句。
__enter__通常执行一些初始化操作,并且该函数的返回值会赋值给可选的 as target 中的target变量。
__exit__执行资源清理工作。它接收三个参数,异常类型,异常实例,和异常栈,根据这些异常信息,__exit__可以选择进行相应的异常处理,并默认抛出异常。如果我们在让__exit__返回True,相当于告诉python:这些异常我都已经处理了,都在掌控之中,您老不必操心。
除了自定义类手动实现两个特殊方法外,还有另一种途径实现一个上下文管理器。 标准库contextlib中提供了一个@contextmanager可以方便的把一个协程函数包装成一个上下文管理器。 《Fluent Python》 书中一个好玩的例子:
@contextmanager def f(): import sys print('欢迎来到镜像的世界') origin_print = sys.stdout.write sys.stdout.write = lambda x: origin_print(x[::-1]) # 初始化:替换系统输入。运行中动态修改、添加类的方法————猴子补丁。 yield '这里的打印都是反向输出' sys.stdout.write = origin_print # 退出时:恢复系统输入 print('Finally I come back') mirror_world = f() with mirror_world as target: print(target)输出结果:
欢迎来到镜像的世界 出输向反是都印打的里这 Finally I come back协程函数中yield之前的所有代码相当于__enter__部分的工作,执行初始化,执行中动态替换了系统的输出功能(猴子补丁特性)。 并且把一个结果绑定到with...as target的target。至此协程函数交出代码执行权,python转而去执行with-block里面的代码。执行完with-block 开始执行yield之后的代码——相当于__exit__的工作,执行资源清理。
至此我们好像实现了一个功能正常的上下文管理器。但别忘了还有异常捕获的机制。。。
在终端中执行mirror_world时,如果with-block中抛出了一个异常,会导致资源清理工作没有进行,之后所有的print仍是反向输出。我们还应做的是把yield行的代码包裹在一个try...except...finally中,在finally-bolck中执行资源清理工作,以保证正常退出(鬼知道用户会在with-block搞什么蛇皮…)。