Python-try...finally... 和 with

try…finally…

当执行一大串代码时,一旦程序代码中某一个部分出错,就会立即停止,同时剩下的其他代码也不会再继续执行了。这就会有一个问题,如果在剩下未执行的代码中有一些事是必须要执行的,比如释放之前分配的内存等等,而因为报错导致之后的代码无法执行,这就会带来内存泄漏等影响。所以使用try...finally...,在try中的代码无论是否出错是否运行被终止,都不会影响finally部分代码的运行。即无论如何finally部分的代码都会执行,这样就可以将一些必须要做的事情放在finally中保证他们的执行

with

try…finally…虽然能解决上述的问题,但是代码过长。还有一个可以用来解决上述问题的方法,那就是with

with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等

要使用with语句,首先要明白上下文管理器这一概念。有了上下文管理器,with语句才能工作

  • 一组与上下文管理器和with语句有关的概念

  1. 上下文管理协议Context Management Protocol:包含方法__enter__()和__exit__(),支持该协议的对象要实现这两个方法。
  2. 上下文管理器Context Management:支持上下文管理协议的对象,这种对象实现了__enter__()和__exit__()这两个方法。上下文管理器定义执行with语句时要建立运行时的上下文,负责执行with语句块上下文中的进入和退出操作。通常使用with语句调用上下文管理器。也可以通过直接调用其方法来使用。
  3. 运行时上下文Runtime Context:由上下文管理器创建,通过上下文管理器的__enter__()和__exit__()方法实现,__enter__()在语句体执行之前进入运行时上下文__exit__()在语句体执行完后从运行时上下文退出。with语句支持运行时上下文这一概念。
  4. 上下文表达式Context Expression:with语句中跟在关键字with之后的表达式为上下文表达式,该表达式应为上下文管理器对象
  5. 语句体With-Body:with语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的__enter__()方法,执行完语句体之后会执行__exit__()方法
  • 基本语法和工作原理

with 语句的语法格式如下:

  • with 语句的语法格式

with context_expression [as target(s)]:

    with-body

这里 context_expression 要返回一个上下文管理器对象,该对象并不赋值给 as 子句中的 target(s) ,如果指定了 as 子句的话,会将上下文管理器的 __enter__() 方法的返回值赋值给 target(s)。target(s) 可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)

Python 对一些内建对象进行改进,加入了对上下文管理器的支持,可以用于 with 语句中,比如可以自动关闭文件、线程锁的自动获取和释放等

假设要对一个文件进行操作,使用 with 语句可以有如下代码:

  • 使用 with 语句操作文件对象
with open(r'somefileName') as somefile:
    for line in somefile:
        print line
        # ...more code

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

  • try...finally...方式操作文件对象
somefile = open(r'somefileName')
try:
    for line in somefile:
        print line
        # ...more code
finally:
    somefile.close()

比较起来,使用 with 语句可以减少编码量。已经加入对上下文管理协议支持的还有模块 threading、decimal 等。

  • with 语句执行过程

context_manager = context_expression
exit = type(context_manager).__exit__  
value = type(context_manager).__enter__(context_manager)
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

使用with语句相对try...finally...来说简洁了很多,而且也不需要每一个用户都去写f.close()来关闭文件了,这是因为with语句在背后做了大量的工作。with语句的context_expression是上下文管理器,这个我们下文会说。with语句中的[as variable]是可选的,如果指定了as variable说明符,则variable是上下文管理器context_expression调用__enter__()函数返回的对象。所以,f并不一定就是expression,而是context_expression.__enter__()的返回值,至于context_expression.__enter__()返回什么就由这个函数来决定了。with-block是执行语句,with-block执行完毕时,with语句会自动进行资源清理,对应上面例子就是with语句会自动关闭文件

下面我们来具体说下with语句在背后默默无闻地到底做了哪些事情。刚才我们说了context_expression是一个上下文管理器,其实现了__enter__和__exit__两个函数。当我们调用一个with语句时,执行过程如下:

  1. 执行 context_expression,生成上下文管理器 context_manager
  2. 调用上下文管理器的 __enter__() 方法;如果使用了 as 子句,则将 __enter__() 方法的返回值赋值给 as 子句中的 target(s)
  3. 执行语句体 with-body
  4. 不管是否执行过程中是否发生了异常,执行上下文管理器的 __exit__() 方法,__exit__() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 __exit__(None, None, None) ;如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 __exit__(exc_type, exc_value, exc_traceback)
  5. 出现异常时,如果 __exit__(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理

with语句不仅可以管理文件,还可以管理锁、连接等等

所以说了这么多,到底什么是上下文管理器?

上下文管理器

上下文管理器就是实现了上下文协议的类,而上下文协议就是一个类要实现__enter__()和__exit__()两个方法。一个类只要实现了__enter__()和__exit__(),我们就称之为上下文管理器

下面我们具体说下这两个方法:

  • __enter__():主要执行一些环境准备工作,同时返回一资源对象。如果上下文管理器open("test.txt")的__enter__()函数返回一个文件对象。__enter__()会根据不同的expression对象返回不同的值
  • __exit__():完整形式为__exit__(type, value, traceback),这三个参数分别为异常类型、异常信息和堆栈。如果执行体语句没有引发异常,则这三个参数均被设为None。否则,它们将包含上下文的异常信息。__exit_()方法返回True或False,分别指示被引发的异常有没有被处理,如果返回False,引发的异常将会被传递出上下文。如果__exit__()函数内部引发了异常,则会覆盖掉执行体的中引发的异常。处理异常时,不需要重新抛出异常,只需要返回False,with语句会检测__exit__()返回False来处理异常。

如果我们要自定义一个上下文管理器,只需要定义一个类并且能实现__enter__()和__exit__()即可。

猜你喜欢

转载自blog.csdn.net/weixin_39721347/article/details/86557679
今日推荐