为了确保即使在出现错误的情况下也能运行某些清理代码,try...finally 语句是

很有用的。这一语句有许多使用场景,例如:

• 关闭一个文件。

• 释放一个锁。

• 创建一个临时的代码补丁。

• 在特殊环境中运行受保护的代码。

with 语句为这些使用场景下的代码块包装提供了一种简单方法。即使该代码块引发了异常,你也可以在其执行前后调用一些代码。例如,处理文件通常采用这种方式:

hosts = open('/etc/hosts') try: ... for line in hosts: ... if line.startswith('#'): ... continue ... print(line.strip()) ... finally: ... hosts.close() ... 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost

利用 with 语句,上述代码可以重写为:

with open('/etc/hosts') as hosts: ... for line in hosts: ... if line.startswith('#'): ... continue ... print(line.strip()) ... 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost 在前面的示例中,open 的作用是上下文管理器,确保即使出现异常也要在执行完 for 循环之后关闭文件。 与这条语句兼容的其他项目是来自 threading 模块的类: • threading.Lock • threading.RLock • threading.Condition • threading.Semaphore • threading.BoundedSemaphore 一般语法和可能的实现 with 语句的一般语法的最简单形式如下:

with context_manager:

代码块

... 此外,如果上下文管理器提供了上下文变量,可以用 as 子句保存为局部变量: with context_manager as context:

代码块

... 注意,多个上下文管理器可以同时使用,如下所示: with A() as a, B() as b: ... 这种写法等价于嵌套使用,如下所示: with A() as a: with B() as b: ... (1)作为一个类 任何实现了上下文管理器协议(context manager protocol)的对象都可以用作上下文管 理器。该协议包含两个特殊方法。 • enter(self):更多内容请访问https://docs.python.org/3.3/reference/datamodel.html #object.enter。 • exit(self, exc_type, exc_value, traceback):更多内容请访问 https://docs.python.org/3.3/reference/datamodel.html#object.__exit__。 简而言之,执行 with 语句的过程如下: • 调用__enter__方法。任何返回值都会绑定到指定的 as 子句。 • 执行内部代码块。 • 调用__exit__方法。 __exit__接受代码块中出现错误时填入的 3 个参数。如果没有出现错误,那么这 3 个 参数都被设为 None。出现错误时,__exit__不应该重新引发这个错误,因为这是调用者 (caller)的责任。但它可以通过返回 True 来避免引发异常。这可用于实现一些特殊的使用 场景,例如下一节将会看到的 contextmanager 装饰器。但在大多数使用场景中,这一 方法的正确行为是执行类似于 finally 子句的一些清理工作,无论代码块中发生了什么, 它都不会返回任何内容。 下面是某个实现了这一协议的上下文管理器示例,以更好地说明其工作原理:

class ContextIllustration: def enter(self): print('entering context') def exit(self, exc_type, exc_value, traceback): print('leaving context') if exc_type is None: print('with no error') else: print('with an error (%s)' % exc_value) 没有引发异常时的运行结果如下:

with ContextIllustration(): ... print("inside") ... entering context inside leaving context with no error 引发异常时的输出如下: with ContextIllustration(): ... raise RuntimeError("raised within 'with'") ... entering context leaving context with an error (raised within 'with') Traceback (most recent call last): File "", line 2, in RuntimeError: raised within 'with' (2)作为一个函数 — contextlib 模块 使用类似乎是实现 Python 语言提供的任何协议最灵活的方法,但对许多使用场景来说 可能样板太多。标准库中新增了 contextlib 模块,提供了与上下文管理器一起使用的辅 助函数。它最有用的部分是 contextmanager 装饰器。你可以在一个函数里面同时提供 __enter__和__exit__两部分,中间用 yield 语句分开(注意,这样函数就变成了生成 器)。用这个装饰器编写前面的例子,其代码如下: from contextlib import contextmanager @contextmanager def context_illustration():

print('entering context') try: yield except Exception as e: print('leaving context') print('with an error (%s)' % e)

需要再次抛出异常

raise else: print('leaving context') print('with no error') 如果出现任何异常,该函数都需要再次抛出这个异常,以便传递它。注意,context_ illustration 在需要时可以有一些参数,只要在调用时提供这些参数即可。这个小的辅 助函数简化了常规的基于类的上下文 API,正如生成器对基于类的迭代器 API 的作用一样。 这个模块还提供了其他 3 个辅助函数。 • closing(element):返回一个上下文管理器,在退出时会调用该元素的 close 方法。例如,它对处理流的类就很有用。 • supress(*exceptions):它会压制发生在 with 语句正文中的特定异常。 • redirect_stdout(new_target)和 redirect_stderr(new_target):它 会将代码块内任何代码的 sys.stdout 或 sys.stderr 输出重定向到类文件 (file-like)对象的另一个文件。