说起上下文管理器,也许会比较陌生,要是说起下面这个例子,相信大家都非常熟悉,这种打开文件的方式就是使用了上下文管理器。
什么是上下文管理器
语法
说明
- context_expr:是支持上下文管理协议的对象,也就是上下文管理器对象,负责维护上下文环境。
- as var:是一个可选部分,通过变量方式保存上下文管理器对象。
- with_suite:需要放在上下文环境中执行的代码块。
为什么要用上下文管理器?
使代码更加优雅,避免了琐碎操作及必要操作步骤的遗忘,一般应用在如下两种场景中:
- 更加优雅的操作资源,如文件、数据库操作。
- 更加优雅的处理异常。
下文中也会就这两种场景一一举例说明。
自定义上下文管理器
想实现一个上下文管理,首先需要了解什么是上下文管理协议(Context Manager Protocol,上下文管理协议包括两个方法:
- __enter__():定义上下文管理器在with语句创建的块的开头应该做什么。注意,_enter__的返回值绑定到with语句的目标,或者as后面的名称。
- __exit__(exc_type, exc_val, exc_tb) :定义上下文管理器在其块被执行(或终止)后应该做什么。它可以用来处理异常、执行清理,或者执行总是在块中的操作之后立即执行的操作。如果块执行成功,exc_type、exc_val和exc_tb将为None。否则,您可以选择处理异常或让用户处理异常;如果您想处理它,请确保在所有操作完成之后,_exit__返回True。如果不想让上下文管理器处理异常,_exit__返回False,则会抛出异常。
也就是说,当我们需要创建一个上下文管理器类型的时候,就需要实现__enter__和__exit__方法,这对方法就称为上下文管理协议,定义了一种运行时上下文环境。
接下来,动手写一个自定义的上下文管理器,示例如下:
上述代码执行结果如下:
通过日志的打印顺序,不难其执行过程,在实际应用中,可以将资源的连接操作放到__enter__中,将资源的关闭操作写在__exit__ 中。
更加优雅的操作资源
在上文中,提到上下文管理器常应用的两个场景,接下来我们看一下第一个场景,更加优雅的操作资源,我们在mysql、sqlite等数据库操作时,经常涉及链接数据库、执行相关操作、关闭链接等操作,让我们上下文管理器是如何优雅的实现数据库链接、执行、关闭操作的,如下:
如上,我们使用上下文管理器优雅的实现数据库的查询,执行上述代码,输出结果如下:
更加优雅的处理异常
介绍完了如何更优雅的操作文件/数据库资源,接下来,看一下上下文管理器常应用到的第二个场景,如何更优雅的处理异常,如下:
以上面的代码为例,“run parse {1}'.format(self.flag)” 将抛出IndexError 异常,接下来我们执行如下代码:
执行上述代码,输出结果如下:
居然没有抛出异常信息,程序运行完成,使用 with 将异常的处理隐藏起来,简化 try/finally 模式,使得代码的可读性更高。这就是上下文管理协议的一个强大之处,异常在__exit__ 中捕获并由我们决定是抛出还是在这__exit__ 中解决。
在__exit__ 里返回 True(默认为返回False,即使代码中没有return),相当于告诉Python解释器,这个异常已经捕获了,不需要往外抛了。若__exit__ 里返回 False,则抛出所触发的异常。如下
执行上述代码,输出结果如下:
我们发现当__exit__ 里返回 False,同时仍然执行了“run close connection ”操作,然后程序抛出所触发的异常,完美。
通过上面几个例子,可以大致总结出with 语句的执行流程:
- 执行context_expr 以获取上下文管理器对象。
- 调用上下文管理器的__enter__方法。
- 若存在 as var 从句,则将 __enter__方法的返回值赋给 var。
- 执行代码块 with_suite。
- 调用上下文管理器的 __exit__方法,如果 with_suite 产生异常,那么该异常的 type、value 和 traceback 会作为参数传给 __exit__,否则传三个None。
- 如果 with_suite 触发异常,且__exit__的返回值等于 False,则抛出该异常。
- 如果 with_suite 触发异常,且 __exit__的返回值等于 True,则忽略该异常。
contextlib 模块
上面自定义上下文管理器,如果应用与一些简单的功能,难免有些繁琐,对于上下文的管理,Python也提供了内建的模块contextlib来实现相同的机制,这种通过生成器和装饰器实现的上下文管理器,看起来比with语句和手动实现上下文管理协议更优雅。
在被装饰的open_file函数里,必须是一个带有yield的生成器,在yield之前的代码,就相当于__enter__方法中的内容,在yield 之后的代码,就相当于__exit__ 方法中的内容。
执行上述代码,运行结果如下:
如果要处理异常,可以改成下面这个样子。
执行上述代码,运行结果如下:
千里之行始于足下,若感兴趣就动手操作一下吧,感谢您的转发、关注支持。
本文暂时没有评论,来添加一个吧(●'◡'●)