深入理解Python上下文管理器,使你的代码更加优雅

说起上下文管理器,也许会比较陌生,要是说起下面这个例子,相信大家都非常熟悉,这种打开文件的方式就是使用了上下文管理器。

什么是上下文管理器

语法

说明

  • context_expr:是支持上下文管理协议的对象,也就是上下文管理器对象,负责维护上下文环境。
  • as var:是一个可选部分,通过变量方式保存上下文管理器对象。
  • with_suite:需要放在上下文环境中执行的代码块。

  • 为什么要用上下文管理器?

    使代码更加优雅,避免了琐碎操作及必要操作步骤的遗忘一般应用在如下两种场景中:

  • 更加优雅的操作资源,如文件、数据库操作。
  • 更加优雅的处理异常。
  • 下文中也会就这两种场景一一举例说明。


    自定义上下文管理器

    想实现一个上下文管理,首先需要了解什么是上下文管理协议(Context Manager Protocol,上下文管理协议包括两个方法:

  • __enter__():定义上下文管理器在with语句创建的块的开头应该做什么。注意,_enter__的返回值绑定到with语句的目标,或者as后面的名称。
  • __exit__(exc_type, exc_val, exc_tb) :定义上下文管理器在其块被执行(或终止)后应该做什么。它可以用来处理异常、执行清理,或者执行总是在块中的操作之后立即执行的操作。如果块执行成功,exc_typeexc_valexc_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__ 方法中的内容。

    执行上述代码,运行结果如下:

    如果要处理异常,可以改成下面这个样子。

    执行上述代码,运行结果如下:


    千里之行始于足下,若感兴趣就动手操作一下吧,感谢您的转发、关注支持。

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2019年8月3日
    下一篇 2019年8月4日

    相关推荐