14.2 上下文管理器
Python 2.6中引入的with语句,可能会让过去的Lisp程序员想起以前经常用到的宏with-*。Python通过使用实现了上下文管理协议的对象,提供了类似的机制。
open函数返回的对象就支持这个协议,这就是经常能看到下面这样的代码的原因:
with open("myfile", "r") as f: line = f.readline()
open返回的对象有两个方法,一个称为__enter__,另一个称为__exit__。它们分别在with块开始和结束时被调用。
一个上下文对象的简单实现如示例14.1所示。
示例 14.1 上下文对象的简单实现
class MyContext(object): def __enter__(self): pass def __exit__(self, exc_type, exc_value, traceback): pass
这段代码什么都不做,但却是合法的。
你想什么时候使用上下文管理器呢?如果对象符合下面的模式,则使用上下文管理协议就比较合适:
(1)调用方法A;
(2)执行一段代码;
(3)调用方法B。
这里希望调用方法B必须总是在调用方法A之后。open函数很好地阐明了这一模式,打开文件并在内部分配一个文件描述符的构造函数便是方法A。释放对应文件描述符的close方法就是方法B。显然,close方法总是应该在实例化文件对象之后进行调用。
contextlib标准库中提供了contextmanager,通过生成器构造__enter__和__exit__方法,从而简化了这一机制的实现。可以使用它实现自己的简单上下文管理器,如示例14.2所示。
示例 14.2 contextlib.contextmanager的简单用法
import contextlib @contextlib.contextmanager def MyContext(): yield
例如,我曾经在Ceilometer(https://launchpad.net/ceilometer)中对我们所建立的流水线(pipeline)架构使用过这种设计模式。简单来说,一个流水线就是一个管道,一方面传入对象,另一方面将对象分发到不同的地方。发送数据的步骤如下。
(1)调用流水线的publish(objects)方法,并传入你的对象作为参数(可以调用任意多次)。
(2)一旦完成,则调用flush()方法以表明当前的发布已经完成。
要注意的是,如果不调用flush()方法,对象将不会被发送到管道中,或者至少不完全发送到管道中。程序员很容易忘记flush()的调用,这将引起程序毫无征兆地中断。
最好能让API提供一个上下文管理器对象,去阻止API的用户犯这种错误。通过示例14.3所示的代码很容易实现。
示例 14.3 在流水线对象上使用上下文管理器
import contextlib class Pipeline(object): def _publish(self, objects): # Imagine publication code here pass def _flush(self): # Imagine flushing code here pass @contextlib.contextmanager def publisher(self): try: yield self._publish finally: self._flush()
现在,当用户在使用流水线发布某些数据时,他们无需使用_publish或者_flush。用户只需请求一个使用了名祖(eponym)函数的publisher并使用它。
pipeline = Pipeline() with pipeline.publisher() as publisher: publisher([1, 2, 3, 4])
当提供一个这样的API时,就不会遇到用户错误。当看到符合的设计模式时,应该尽量用上下文管理器。
在某些情况下,同时使用多个上下文管理器是很有用的。例如,同时打开两个文件以复制它们的内容,如示例14.4所示。
示例 14.4 同时打开两个文件
with open("file1", "r") as source: with open("file2", "w") as destination: destination.write(source.read())
记住with语句可以支持多个参数,所以应该像示例14.5这样写。
示例 14.5 通过一条with语句同时打开两个文件
with open("file1", "r") as source, open("file2", "w") as destination: destination.write(source.read())
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论