51Testing软件测试论坛

标题: 浅学 Python 的 with 语句 [打印本页]

作者: 一月蔷薇_456    时间: 2018-2-26 16:03
标题: 浅学 Python 的 with 语句
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. with context_expression [as target(s)]:
  2.     with-body
复制代码
假设对一个文件进行操作,使用with语句,代码如下:
  1. with open(r'somefileName') as somefile:
  2.     for line in somefile:
  3.         print line
  4.         # ...more code
复制代码
不管在处理文件过程中是否发生异常,都能保证with语句执行完之后关闭文件句柄。如果使用传统
的try/finally范式,则代码如下:
  1. somefile = open(r'somefileName')
  2. try:
  3.     for line in somefile:
  4.         print line
  5.         #...more code
  6. finally:
  7.     somefile.close()
复制代码
相比较下,采用with语句可减少代码量。

工作原理

PEP 0343 对 with 语句的实现进行了描述。with 语句的执行过程类似如下代码块:
  1. context_manager = context_expression
  2. value = type(context_manager).__enter__(context_manager)
  3. exit = type(context_manager).__exit__
  4. exc = True # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
  5. try:
  6.     try:
  7.         target = value # 如果使用了as子句
  8.         with-body # 执行with-body
  9.     except:
  10.     # 执行过程中有异常发生
  11.     exc = False
  12.     # 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
  13.     # 由外层代码对异常进行处理
  14.     if not exit(context_manager, *sys.exc_info()):
  15.         raise
  16. finally:
  17.     # 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
  18.     # 或者忽略异常退出
  19.     if exc:
  20.         exit(context_manager, None, None, None)
  21.     # 缺省返回 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. class DummyResource:
  2.     def __init__(self, tag):
  3.         self.tag = tag
  4.         print('Resource[%s]' % tag)
  5.     def __enter__(self):
  6.         print('[Enter %s]: Allocate resource.' % self.tag)
  7.         return self  # 可以返回不同的对象
  8.     def __exit__(self, exc_type, exc_value, exc_tb):
  9.         print('[Exit %s]: Free resource.' % self.tag)
  10.         if exc_tb is None:
  11.             print('[Exit %s]: Exited without exception.' % self.tag)
  12.         else:
  13.             print('[Exit %s]: Exited with exception raised.' % self.tag)
  14.             return False  # 可以省略,缺省的None被看作是False
复制代码
使用自定义的支持with语句的对象
  1. with DummyResource('Normal'):
  2.     print('[with-body] Run without exceptions.')
  3. 运行结果如下;

  4. Resource[Normal]
  5. [Enter Normal]: Allocate resource.
  6. [with-body] Run without exceptions.
  7. [Exit Normal]: Free resource.
  8. [Exit Normal]: Exited without exception.
  9. 正常执行时会先执行__enter__(),再执行完语句体 with-body,最后执行 __exit__()方法释放资源。

  10. with DummyResource('With-Exception'):
  11.     print('[with-body] Run with exception.')
  12.     raise Exception
  13.     print('[with-body] Run with exception. Failed to finish statement-body')
  14. 运行结果如下:

  15. Resource[With-Exception]
  16. [Enter With-Exception]: Allocate resource.
  17. [with-body] Run with exception.
  18. [Exit With-Exception]: Free resource.
  19. [Exit With-Exception]: Exited with exception raised.
  20. Traceback (most recent call last):
  21.   File "C:/DummyResource.py", line 23, in <module>
  22.     raise Exception
  23. Exception

  24. Process finished with exit code 1
复制代码
执行with-body语句发生异常,with-body语句体未执行完毕,但资源释放了,并抛出异常由with-body
之外代码逻辑来捕获处理。

可以自定义上下文管理器来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。
Python 在线文档 Writing Context Managers 提供了一个针对数据库连接进行管理的上下文管理器的
简单范例。


作者: 梦想家    时间: 2018-2-26 17:26

作者: Miss_love    时间: 2020-12-30 09:35
支持分享




欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2