阅读耗时:9分钟
目录
编写自定义上下文管理器
从生成器到上下文管理器
将上下文管理器编写为装饰器
嵌套式上下文管理器
组合式上下文管理器
利用用上下文管理器创建SQLAlchemy session
利用上下文管理器捕获异常
利用上下文管理器处理网络请求
备注
资源
Python的上下文管理器非常适合调节资源的利用率和管理内存的开销。通常,它是以关键字with开头的语句:
在上面的例子中,当文件写入完成时,file.txt会自动关闭。这等价于:
编写自定义上下文管理器
要编写自定义上下文管理器,需要创建一个包含__enter__和__exit__方法的类。让我们重新创建一个自定义的上下文管理器,它将执行与上述相同的工作流程。
可以像常规上下文管理器一样使用上述类。
从生成器到上下文管理器
上述通过使用__enter__和__exit__方法来编写类仅仅是创建上下文管理器的途径之一。另一个途径是通过导入contextlib.contextmanager库来实现。该库使用生成器创建上下文管理器。方法如下所示:
然后,与关键字with配合,实现其应有功能。
它可以解释为:
所代表的设置类代码位于try...finally之前。注意关键字yield所在的位置,它所定义的值将赋值给as之后的变量,并保持值的稳定。如果存在一个未处理的异常,那么它将在生成器中yield位置被重新引发,然后执行finally代码块;反之,程序会优雅地运行下去,直至finally代码块运行结束。
让我们使用contextmanager装饰器实现CustomFileOpen的上下文管理器。
用法相同:
将上下文管理器编写为装饰器
我们还可以将上下文管理器重写为装饰器。为此,在定义类时,须继承contextlib.ContextDecorator类。下面就编写一个名为RunTime的装饰器,该装饰器将能打开文件。并实现以下功能:
打印向用户提供的功能说明
打印运行该功能所需的时间
使用方法:
返回方法:
还可以通过contextlib.contextmanager创建功能相同的装饰器。
嵌套上下文管理器
上下文管理器可以进行嵌套,如下所示:
注意关闭的顺序。上下文管理器拥有类似堆栈的逻辑,应按相反的顺序退出。任何一个上下文管理器都可以处理异常,若此异常已经被某个管理器所处理,那么其它的管理器将不会收到有关此异常的任何信息;因此,如果发生异常,诸多嵌套的上下文管理器的逻辑顺序就成了运维的重要依据;而问题的另一方面,我们可以使用__exit__方法来引发异常,然后使用上下文管理器对其进行处理。
组合多个上下文管理器
我们可以组合多个上下文管理器。考虑这两个管理器。
现在,使用装饰器语法将两者结合起来。以下函数采用上面的定义管理器a和b并返回组合的上下文管理器ab。
可以用作:
利用contextlib.ExitStack库可以为诸多的上下文管理器提供组合支持。下面我们就使用ExitStack重写上下文管理器ab。此函数将各个上下文管理器及其参数作为元组,并返回组合的管理器。
如果要优雅地管理多个资源,也可以使用ExitStack。举个例子,假设需要根据目录中多个文件的内容来创建一个列表。那么让我们看看,如何通过强大的资源管理来避免意外的内存泄漏。
使用上下文管理器创建SQLAlchemy session
SQLALchemy,是Python的SQL工具包和最有名的ORM架构,它使用session进行查询。 session将查询转换为事务并使其原子化。上下文管理器可以非常优雅编写事务型session。SQLAlchemy中的基本查询工作流程如下所示:
上面使用了上下文管理器创建了一个SQLite连接和一个session_scope。 session_scope负责自动处理异常情况下的提交和回滚。session_scope可通过以下方式进行查询:
使用上下文管理器对异常的抓取规则进行设置
这是我绝对喜欢的管理器功能。假设需要编写一个函数,希望它能避免具有类似日志功能的异常处理逻辑对核心逻辑的混淆, 那么可以编写一个装饰器类型的上下文管理器,来将普通处理逻辑与主逻辑解耦。接下来,我们写一个装饰器,它可以同时处理ZeroDivisionError和TypeError。
现在,在发生异常的函数中使用它。
看到了吧,errhandler装饰器正在完成繁重的工作。很整洁吧?
以下是使用上下文管理器将错误处理异常与主逻辑解耦的更复杂的示例。它还从main方法中隐藏了详细的日志记录逻辑。
这将返回
利用上下文管理器实现Http请求的持久化
上下文管理器的另一个很好的应用是使参数在多个http请求中保持不变。 Python的请求库有一个Session对象,如果您要向同一主机发出多个请求,那么底层的TCP连接将被重用,从而可以显着提高性能。以下示例直接取自requests的官方文档。我们在requests中保留一些cookie。
结果:
备注
所有代码段均已针对python 3.8更新。为避免冗余,我故意排除了带语句嵌套的示例,现在不建议使用contextlib.nested函数来创建嵌套的上下文管理器。
英文原文:https://rednafi.github.io/digressions/python/2020/03/26/python-contextmanager.html
译者:sky