1 模块简介在数年前,Python 2.5 加入了一个非常特殊的关键字,就是with。with语句允许开发者创建上下文管理器。什么是上下文管理器?上下文管理器就是允许你可以自动地开始和结束一些事情。例如,你可能想要打开一个文件,然后写入一些内容,最后再关闭文件。这或许就是上下文管理器中一个最经典的示例。事实上,当你利用with语句打开一个文件时,Python替你自动创建了一个上下文管理器。 with open('test/test.txt','w') as f_obj: f_obj.write('hello') 如果你使用的是Python 2.4,你不得不以一种老的方式来完成这个任务。 f_obj = open('test/test.txt','w')f_obj.write('hello')f_obj.close 上下文管理器背后工作的机制是使用Python的方法:__enter__和__exit__。让我们尝试着去创建我们的上下文管理器,以此来了解上下文管理器是如何工作的。 2 模块使用2.1 创建一个上下文管理器类与其继续使用Python打开文件这个例子,不如我们创建一个上下文管理器,这个上下文管理器将会创建一个SQLite数据库连接,当任务处理完毕,将会将其关闭。下面就是一个简单的示例。 import sqlite3class DataConn: def __init__(self,db_name): self.db_name = db_name def __enter__(self): self.conn = sqlite3.connect(self.db_name) return self.conn def __exit__(self,exc_type,exc_val,exc_tb): self.conn.close if exc_val: raiseif __name__ == '__main__': db = 'test/test.db' with DataConn(db) as conn: cursor = conn.cursor 在上述代码中,我们创建了一个类,获取到SQLite数据库文件的路径。__enter__方法将会自动执行,并返回数据库连接对象。现在我们已经获取到数据库连接对象,然后我们创建光标,向数据库写入数据或者对数据库进行查询。当我们退出with语句的时候,它将会调用__exit__方法用于执行和关闭这个连接。 让我们使用其它的方法来创建上下文管理器。 2.2 利用contextlib创建一个上下文管理器Python 2.5 不仅仅添加了with语句,它也添加了contextlib模块。这就允许我们使用contextlib的contextmanager函数作为装饰器,来创建一个上下文管理器。让我们尝试着用它来创建一个上下文管理器,用于打开和关闭文件。 from contextlib import contextmanager@contextmanagerdef file_open(path): try: f_obj = open(path,'w') yield f_obj except OSError: print('We had an error!') finally: print('Closing file') f_obj.closeif __name__ == '__main__': with file_open('test/test.txt') as fobj: fobj.write('Testing context managers') 在这里,我们从contextlib模块中引入contextmanager,然后装饰我们所定义的file_open函数。这就允许我们使用Python的with语句来调用file_open函数。在函数中,我们打开文件,然后通过yield,将其传递出去,最终主调函数可以使用它。 一旦with语句结束,控制就会返回给file_open函数,它继续执行yield语句后面的代码。这个最终会执行finally语句--关闭文件。如果我们在打开文件时遇到了OSError错误,它就会被捕获,最终finally语句依然会关闭文件句柄。 contextlib.closing(thing) contextlib模块提供了一些很方便的工具。第一个工具就是closing类,一旦代码块运行完毕,它就会将事件关闭。Python官方文档给出了类似于以下的一个示例, >>> from contextlib import contextmanager>>> @contextmanager... def closing(db):... try:... yield db.conn... finally:... db.close 在这段代码中,我们创建了一个关闭函数,它被包裹在contextmanager中。这个与closing类相同。区别就是,我们可以在with语句中使用closing类本身,而非装饰器。让我们看如下的示例, >>> from contextlib import closing>>> from urllib.request import urlopen>>> with closing(urlopen('http://www.google.com')) as webpage:... for line in webpage:... pass 在这个示例中,我们在closing类中打开一个url网页。一旦我们运行完毕with语句,指向网页的句柄就会关闭。 contextlib.suppress(*exceptions) 另一个工具就是在Python 3.4中加入的suppress类。这个上下文管理工具背后的理念就是它可以禁止任意数目的异常。假如我们想忽略FileNotFoundError异常。如果你书写了如下的上下文管理器,那么它不会正常运行。 >>> with open('1.txt') as fobj:... for line in fobj:... print(line)...Traceback (most recent call last): File ' 正如你所看到的,这个上下文管理器没有处理这个异常,如果你想忽略这个错误,你可以按照如下方式来做, >>> from contextlib import suppress>>> with suppress(FileNotFoundError):... with open('1.txt') as fobj:... for line in fobj:... print(line) 在这段代码中,我们引入suppress,然后将我们要忽略的异常传递给它,在这个例子中,就是FileNotFoundError。如果你想运行这段代码,你将会注意到,文件不存在时,什么事情都没有发生,也没有错误被抛出。请注意,这个上下文管理器是可重用的,2.4章节将会具体解释。 contextlib.redirect_stdout/redirect_stderr contextlib模块还有一对用于重定向标准输出和标准错误输出的工具,分别在Python 3.4 和3.5 中加入。在这些工具被加入之前,如果你想对标准输出重定向,你需要按照如下方式操作, import syspath = 'test/test.txt'with open(path,'w') as fobj: sys.stdout = fobj help(sum) 利用contextlib模块,你可以按照如下方式操作, from contextlib import redirect_stdoutpath = 'test/test.txt'with open(path,'w') as fobj: with redirect_stdout(fobj): help(redirect_stdout) 在上面两个例子中,我们均是将标准输出重定向到一个文件。当我们调用Python的help函数,不是将信息输出到标准输出上,而是将信息保存到重定向的文件中。你也可以将标准输出重定向到缓存或者从用接口如Tkinter或wxPython中获取的文件控制类型上。 2.3 ExitStackExitStack是一个上下文管理器,允许你很容易地与其它上下文管理结合或者清除。这个咋听起来让人有些迷糊,我们来看一个Python官方文档的例子,或许会让我们更容易理解它。 >>> from contextlib import ExitStack>>> filenames = ['1.txt','2.txt']>>> with ExitStack as stack:... file_objects = [stack.enter_context(open(filename)) for filename in filenames] 这段代码就是在列表中创建一系列的上下文管理器。ExitStack维护一个寄存器的栈。当我们退出with语句时,文件就会关闭,栈就会按照相反的顺序调用这些上下文管理器。 Python官方文档中关于contextlib有很多示例,你可以学习到如下的技术点:
大部分你所创建的上下文管理器仅仅只能在with语句中使用一次,示例如下: >>> from contextlib import contextmanager>>> @contextmanager... def single:... print('Yielding')... yield... print('Exiting context manager')...>>> context = single>>> with context:... pass...YieldingExiting context manager>>> with context:... pass...Traceback (most recent call last): File ' 在这段代码中,我们创建了一个上下文管理器实例,并尝试着在Python的with语句中运行两次。当第二次运行时,它抛出了RuntimeError。 但是如果我们想运行上下文管理器两次呢?我们需要使用可重用的上下文管理器。让我们使用之前所用过的redirect_stdout这个上下文管理器作为示例, >>> from contextlib import redirect_stdout>>> from io import StringIO>>> stream = StringIO>>> write_to_stream = redirect_stdout(stream)>>> with write_to_stream:... print('Write something to the stream')... with write_to_stream:... print('Write something else to stream')...>>> print(stream.getvalue)Write something to the streamWrite something else to stream 在这段代码中,我们创建了一个上下文管理器,它们均向StringIO(一种内存中的文件流)写入数据。这段代码正常运行,而没有像之前那样抛出RuntimeError错误,原因就是redirect_stdout是可重用的,允许我们可以调用两次。当然,实际的例子将会有更多的函数调用,会更加的复杂。一定要注意,可重用的上下文管理器不一定是线程安全的。如果你需要在线程中使用它,请先仔细阅读Python的文档。 2.5 总结上下文管理器很有趣,也很方便。我经常在自动测试中使用它们,例如,打开和关闭对话。现在,你应该可以使用Python内置的工具去创建你的上下文管理器。你还可以继续阅读Python关于contextlib的文档,那里有很多本文没有覆盖到的知识。 3 Reference |
|