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 语句的语法格式如下:
with context_expression [as target(s)]:
with-body
假设对一个文件进行操作, 使用 with 语句, 代码如下:
with open(r'somefileName') as somefile:
for line in somefile:
print line
# ...more code
不管在处理文件过程中是否发生异常, 都能保证 with 语句执行完之后关闭文件句柄如果使用传统的 try/finally 范式, 则代码如下:
somefile = open(r'somefileName')try:
for line in somefile:
print line
#...more code
finally:
somefile.close()
相比较下, 采用 with 语句可减少代码量
工作原理
PEP 0343 对 with 语句的实现进行了描述 with 语句的执行过程类似如下代码块:
context_manager = context_expressionvalue = 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()):
raisefinally:
# 正常退出, 或者通过 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__() 方法
下面通过一个简单的示例来演示如何构建自定义的上下文管理器
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 语句的对象
with DummyResource('Normal'):
print('[with-body] Run without exceptions.')
运行结果如下;
Resource[Normal][Enter Normal] : Allocate resource. [with - body] Run without exceptions. [Exit Normal] : Free resource. [Exit Normal] : Exited without exception.
正常执行时会先执行__enter__(), 再执行完语句体 with-body, 最后执行 __exit__() 方法释放资源
with DummyResource('With-Exception'):
print('[with-body] Run with exception.')
raise Exception
print('[with-body] Run with exception. Failed to finish statement-body')
运行结果如下:
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 提供了一个针对数据库连接进行管理的上下文管理器的简单范例
来源: https://sdk.cn/news/8078