With语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后关闭,线程中锁的获取和释放等。
术语
介绍一组与上下文管理器和with语句有关的概念。
上下文管理协议(Context Management Protocol):包含方法__enter__()
和__exit__()
,支持该协议的对象要实现这两个方法。
上下文管理器(Context Manager):支持上下文管理协议的对象,定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。
运行时上下文(Runtime Context):由上下文管理器创建,通过上下文管理器的__enter__()
和__exit__()
方法实现。__enter__()
方法在语句体执行之前进入运行时上下文;__exit__()
在语句体执行完后从运行时上下文退出。
上下文表达式(Context Expression):with语句中关键字with的表达式,返回一个上下文管理器对象
语句体(With-Body):with语句下的代码块,在执行语句体之前会调用上下文管理器的__enter__()
方法,执行完语句体之后会执行__exit__()
方法。
基本语法
with语句的语法格式如下:
1 2 3 |
with context_expression [as target(s)]: with-body |
假设对一个文件进行操作,使用with语句,代码如下:
1 2 3 4 5 |
with open(r'somefileName') as somefile: for line in somefile: print line # ...more code |
不管在处理文件过程中是否发生异常,都能保证with语句执行完之后关闭文件句柄。如果使用传统的try/finally范式,则代码如下:
1 2 3 4 5 6 7 8 |
somefile = open(r'somefileName') try: for line in somefile: print line #...more code finally: somefile.close() |
相比较下,采用with语句可减少代码量。
工作原理
PEP 0343 对 with 语句的实现进行了描述。with 语句的执行过程类似如下代码块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
context_manager = context_expression value = type(context_manager).__enter__(context_manager) exit = type(context_manager).__exit__ exc = True # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理 try: try: target = value # 如果使用了as子句 with-body # 执行with-body except: # 执行过程中有异常发生 exc = False # 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常 # 由外层代码对异常进行处理 if not exit(context_manager, *sys.exc_info()): raise finally: # 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出 # 或者忽略异常退出 if exc: exit(context_manager, None, None, None) # 缺省返回 None,None 在布尔上下文中看做是 False |
- 执行
context_expression
,生成上下文管理器context_manager
- 调用上下文管理器的
__enter__()
方法;若使用了as
子句,则将__enter__()
方法的返回值赋值给as
子句中的target(s)
。target(s)
是单个变量,或者是元组(必须加“()”) - 执行语句体
with-body
- 不管执行过程中是否发生异常,都会执行上下文管理器的
__exit__()
方法。__exit__()
方法负责执行“清理”工作,如释放资源等。若执行过程中没有出现异常,或者语句体with-body
中执行语句break/continue/return
,则以None
作为参数调用__exit__(None, None, None)
;若执行过程中出现异常,则使用sys.exc_info
得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)
- 执行语句体
with-body
出现异常,若__exit__(exc_type, exc_value, exc_traceback)
返回False
,则重新抛出异常,with
,则忽略此异常,不再对异常进行处理。语句之外的逻辑处理异常;若返回True
自定义上下文管理器
开发人员可以自定义支持上下文管理协议的类,实现上下文管理协议所需的两个方法:__exit__()
和__exit__(exc_type, exc_value, exc_traceback)
。
注意 上下文管理器必须同时提供 __enter__()
和__exit__()
方法的定义,缺少任何一个都会导致AttributeError
;with
语句先检查是否定义了 __exit__()
方法,然后检查是否定义了__enter__()
方法。
下面通过一个简单的示例来演示如何构建自定义的上下文管理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class DummyResource: def __init__(self, tag): self.tag = tag print('Resource[%s]' % tag) def __enter__(self): print('[Enter %s]: Allocate resource.' % self.tag) return self # 可以返回不同的对象 def __exit__(self, exc_type, exc_value, exc_tb): print('[Exit %s]: Free resource.' % self.tag) if exc_tb is None: print('[Exit %s]: Exited without exception.' % self.tag) else: print('[Exit %s]: Exited with exception raised.' % self.tag) return False # 可以省略,缺省的None被看作是False |
使用自定义的支持with
语句的对象
1 2 3 |
with DummyResource('Normal'): print('[with-body] Run without exceptions.') |
运行结果如下;
1 2 3 4 5 6 |
Resource[Normal] [Enter Normal]: Allocate resource. [with-body] Run without exceptions. [Exit Normal]: Free resource. [Exit Normal]: Exited without exception. |
正常执行时会先执行__enter__()
,再执行完语句体 with-body
,最后执行 __exit__()
方法释放资源。
1 2 3 4 5 |
with DummyResource('With-Exception'): print('[with-body] Run with exception.') raise Exception print('[with-body] Run with exception. Failed to finish statement-body') |
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
Resource[With-Exception] [Enter With-Exception]: Allocate resource. [with-body] Run with exception. [Exit With-Exception]: Free resource. [Exit With-Exception]: Exited with exception raised. Traceback (most recent call last): File "C:/DummyResource.py", line 23, in <module> raise Exception Exception Process finished with exit code 1 |
执行with-body
语句发生异常,with-body
语句体未执行完毕,但资源释放了,并抛出异常由with-body
之外代码逻辑来捕获处理。
可以自定义上下文管理器来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。Python 在线文档 Writing Context Managers 提供了一个针对数据库连接进行管理的上下文管理器的简单范例。
其他
特别感谢这篇文章浅谈 Python 的 with 语句,让我基本了解了with
语句的用法。