Python学习笔记 - 理解with语句

Posted by zihengCat on 2018-05-23

前言

Python的with语句是从 Python 2.5 开始引入的一种与异常处理相关的功能。with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

基本语法

with语句的基本语法格式如下所示,with后接上下文表达式(Context Expression),如果指定了as子句的话,Python会将上下文管理器的__enter__()方法的返回值赋给target(s)target(s)可以是单个变量,也可以是()元组(不能是仅仅由,分隔的变量列表,必须加())。

# 语句头
with context_expression [as target(s)]:
    # 语句体
    with-body

代码清单:with语句语法格式

Python 中很多内建对象与标准库都支持with语句,比如可以自动关闭文件、线程锁的自动获取和释放等。以文件操作为例,使用with语句可以写如下代码:

with open(r'filename') as f:
    # do with file f
    # ...

代码清单:使用with语句操作文件对象

这里使用了with语句,不管在处理文件过程中是否发生异常,都能保证with语句执行完毕后关闭打开的文件句柄。使用传统try...except...finally异常处理结构,则可以写出如下代码:

f = open(r'filename')
try:
    # do with file f
    # ...
finally:
    f.close()

代码清单:传统方式操作文件对象

比较起来,使用with语句可以减少代码量,还可以使我们在操作资源对象时不会因忘记释放资源而出现问题(with语句在结束时会执行__exit__()方法自动回收资源)。

工作原理

要理解with语句的工作原理,我们可以参考下列代码:

context_manager = context_expression
# 取得但不调用退出方法
exit = type(context_manager).__exit__
value = type(context_manager).__enter__(context_manager)
# True 表示正常执行,即便有异常也忽略
# False 表示重新抛出异常,需要对异常进行处理
exc = True
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:
        # None 在上下文中被看做是 False
        # 默认返回 None
        exit(context_manager, None, None, None)

代码清单:with语句执行过程

具体执行步骤为:

  1. 执行context_expression,生成上下文管理器context_manager

  2. 调用上下文管理器的__enter__()方法;如果使用了as子句,则将__enter__()方法的返回结果赋值给as子句中的target(s)

  3. 执行with语句体with-body

  4. 不管是否执行过程中是否发生了异常,都会执行上下文管理器的__exit__()方法,该方法负责执行”清理”动作(如释放资源等)。

  5. 出现异常时,将异常传递给__exit__()方法进行处理,如果返回False(异常处理失败),则会重新抛出异常,让with之外的语句逻辑来处理异常。如果返回True(异常处理成功),则忽略异常,不再对异常进行处理。

参考资料