Python上下文管理器
上下文管理器
有时候我们需要在当程序在语句块中运行时保持某种状态,并且在离开语句块后结束这种状态。所以,事实上上下文管理器的任务是 – 代码块执行前准备,代码块执行后收拾。
上下文管理器允许你在有需要的时候,精确地分配和释放资源。使用上下文管理器最广泛的案例就是with语句了。
上下文管理器的一个常见用例,是资源的加锁和解锁,以及关闭已打开的文件。
基于类的实现
一个上下文管理器的类,最起码要定义enter和exit方法。
我们的exit函数接受三个参数。这些参数对于每个上下文管理器类中的exit方法都是必须的。我们来谈谈在底层都发生了什么。
- 1.with语句先暂存了File类的exit方法
- 2.然后它调用File类的enter方法
- 3.enter方法打开文件并返回给with语句
- 4.打开的文件句柄被传递给opened_file参数
- 5.我们使用.write()来写文件
- 6.with语句调用之前暂存的exit方法
- 7.exit方法关闭了文件
处理异常
我们还没有谈到exit方法的这三个参数:type, value和traceback。
在第4步和第6步之间,如果发生异常,Python会将异常的type,value和traceback传递给exit方法。
它让exit方法来决定如何关闭文件以及是否需要其他步骤。
我们来列一下,当异常发生时,with语句会采取哪些步骤。
- 1.它把异常的type,value和traceback传递给exit方法
- 2.它让exit方法来处理异常
- 3.如果exit返回的是True,那么这个异常就被优雅地处理了。
- 4.如果exit返回的是True以外的任何东西,那么这个异常将被with语句抛出。
在我们的案例中,exit方法返回的是None(如果没有return语句那么方法会返回None)。因此,with语句抛出了那个异常。
我们的exit方法返回了True,因此没有异常会被with语句抛出。
基于生成器的实现
我们还可以用装饰器(decorators)和生成器(generators)来实现上下文管理器。
Python有个contextlib模块专门用于这个目的。我们可以使用一个生成器函数来实现一个上下文管理器,而不是使用一个类。
让我们看看一个基本的,没用的例子:
在这个例子中我们还没有捕捉可能产生的任何异常。它的工作方式和之前的方法大致相同。
让我们小小地剖析下这个方法。
- 1.Python解释器遇到了yield关键字。因为这个缘故它创建了一个生成器而不是一个普通的函数。
- 2.因为这个装饰器,contextmanager会被调用并传入函数名(open_file)作为参数。
- 3.contextmanager函数返回一个以GeneratorContextManager对象封装过的生成器。
- 4.这个GeneratorContextManager被赋值给open_file函数,我们实际上是在调用GeneratorContextManager对象。
那现在我们既然知道了所有这些,我们可以用这个新生成的上下文管理器了,像这样:
如果你希望在上下文管理器中使用“as”关键字,那么就用yield返回你需要的值,它将通过as关键字赋值给新的变量。
谈一些关于上下文库(contextlib)的内容
contextlib是一个Python模块,作用是提供更易用的上下文管理器。
contextlib.closing
假设我们有一个创建数据库函数,它将返回一个数据库对象,并且在使用完之后关闭相关资源(数据库连接会话等)
我们可以像以往那样处理或是通过上下文管理器:
contextlib.closing方法将在语句块结束后调用数据库的关闭方法。
contextlib.nested
假设我们有两个文件,一个读一个写,需要进行拷贝。
以下是不提倡的:
可以通过contextlib.nested进行简化:
在Python2.7中这种写法被一种新语法取代:
contextlib.contextmanager
对于Python高级玩家来说,任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看
做在代码块执行前的操作,而任何yield之后的操作都可以放在exit函数中。
如果你希望在上下文管理器中使用“as”关键字,那么就用yield返回你需要的值,它将通过as关键字赋值给新的变量。
contextlib库中除了上述的几个常见的上下文管理器函数以外,还有contextlib.suppress(*exceptions)、contextlib.redirect_stdout / redirect_stderr、contextlib.ContextDecorator、contextlib.ExitStack等函数可以适用于不同的情景。