10.4 上下文管理
10.4.1 with语句
如上所述的标准化的try-except和try-finally可以使得程序更加“Pythonic”,其含义是,在许多的其他特性之外,写得更加轻松,读得自在。Python对隐藏细节已经做了大量的工作,因此需要你操心的仅是如何解决你所遇到的问题(你能假想移植一个复杂的Python应用到C++或Java吗?)。
另一个隐藏低层次的抽象的例子是with语句,它在Python 2.6中正式启用(Python2.5尝试性的引入了with,并对使用with作为标识符的应用程序发出这样的警告——在Python 2.6中,with将会成为关键字。如果你想在Python 2.5使用with语句,你必须用from__future_import with_statement来导入它)。
类似于try-except-finally,with语句也是用来简化代码的,这与用try-except和try-finally所想达到的目的前后呼应。try-except和try-finally的一种特定的配合用法是保证共享的资源的唯一分配,并在任务结束的时候释放它。比如文件(数据、日志、数据库等等)、线程资源、简单同步、数据库连接,等等。with语句的目标就是应用在这种场景。
然而,with语句的目的在于从流程图中把try、except和finally关键字和资源分配释放相关代码统统去掉,而不是像try-except-finally那样仅仅简化代码使之易用。with语法的基本用法如下:
看起来如此简单,但是其背后还有一些工作要做。这并不如看上去的那么容易,因为你不能对Python的任意符号使用with语句。它仅能工作于支持上下文管理协议(context managementprotocol)的对象。这显然意味着只有内建了“上下文管理”的对象可以和with一起工作。我们过一会再来阐明它的含义。
现在,正如一个新的游戏硬件,每当有一个新的特性推出时,第一时间总有人开发出相应的新游戏,从而你打开盒子就可以开始玩了。类似,目前已经有了一些支持该协议的对象。下面是第一批成员的简短列表:
file
decimal.Context
thread.LockType
threading.Lock
threading.RLock
threading.Condition
threading.Semaphore
threading.BoundedSemaphore
既然file是上面的列表上的第一个也是最易于演示的,下面就给出一段和with一起使用的代码片段。
这个代码片段干了什么呢,这是Python,因而你很可能已经猜到了。它会完成准备工作,比如试图打开一个文件,如果一切正常,把文件对象赋值给f.然后用迭代器遍历文件中的每一行,当完成时,关闭文件。无论的在这一段代码的开始,中间,还是结束时发生异常,会执行清理的代码,此外文件仍会被自动的关闭。
因为已经从你手边拿走了一堆细节,所以实际上只是进行了两层处理:
第一,发生用户层——和in类似,你所需要关心的只是被使用的对象;
第二,在对象层。既然这个对象支持上下文管理协议,它干的也就是“上下文管理”。
10.4.2 *上下文管理协议
除非你打算自定义可以和with一起工作的类,比如:别的程序员会在他们的设计的应用中使用你的对象。绝大多数Python程序员仅仅需要使用with语句,可以跳过这一节。
我们不打算在这里对上下文管理做深入且详细的探讨,但会介绍兼容协议所必须的对象类型与功能,使其能和with一起工作。
前面,我们在例子中描述了一些关于协议如何和文件对象协同工作。让我们在此进一步地研究。
1.上下文表达式(context_expr),上下文管理器
当with语句执行时,便执行上下文符号(译者注:就是with与as间内容)来获得一个上下文管理器。上下文管理器的职责是提供一个上下文对象。这是通过调用context()方法来实现的。该方法返回一个上下文对象,用于在with语句块中处理细节。有点需要注意的是上下文对象本身就可以是上下文管理器。所以contextexpr既可以为一个真正的上下文管理器,也可以是一个可以自我管理的上下文对象。在后一种情况时,上下文对象仍然有_context()方法,返回其自身,如你所想。
2.上下文对象,with语句块
一旦我们获得了上下文对象,就会调用它的enter()方法。它将完成with语句块执行前的所有准备工作。你可以注意到在上面的with行的语法中有一个可选的as声明变量跟随在contextexpr之后。如果提供提供了变量,以_enter()返回的内容来赋值;否则,丢弃返回值。在我们的文件对象例子中,上下文对象的enter()返回文件对象并赋值给f。
现在,执行了with语句块。当with语句块执行结束,无论是“和谐地”还是由于异常,都会调用上下文对象的exit()方法。exit()有三个参数。如果with语句块正常结束,三个参数全部是None。如果发生异常,三个参数的值的分别等于调用sys.exc_info()函数(见10.12)返回的三个值:类型(异常类)、值(异常实例)和跟踪记录(traceback),相应的跟踪记录对象。
你可以自己决定如何在exit()里面处理异常。惯例是当你处理完异常时不返回任何值,或返回None,或返回其他布尔值为False对象。这样可以使异常抛给你的用户来处理。如果你明确地想屏蔽这个异常,返回一个布尔为True的值。如果没有发生异常或你在处理异常后返回True,程序会继续执行with子句后的下一段代码。
因为上下文管理器主要作用于共享资源,你可以想象到enter()和exit()方法基本是干的需要分配和释放资源的低层次工作,比如:数据库连接、锁分配、信号量加减、状态管理、打开/关闭文件、异常处理等。
为了帮助你编写对象的上下文管理器,有一个contextlib模块,包含了实用的functions/decorators,你可以用在你的函数/对象上而不用去操心关于类或context()、enter()、enter()和exit()这些方法的实现。
想了解更多关于上下文管理器的信息,请查看官方的Python文档的with语法和contextlib模块、类的指定方法(与 with和 contexts相关的)、PEP 343和《What’s New in Python 2.5 (Python 2.5的更新)》的文档。