with 语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要经过 from __future__ import with_statement 导入后才可使用),从 2.6 版本开始缺省可用。with 语句适用于对资源进行访问的场合,确保无论使用过程当中是否发生异常都会执行必要的“清理”操做,释放资源,好比文件使用后自动关闭、线程中锁的自动获取和释放等。html
要使用 with 语句,首先要明白上下文管理器这一律念。有了上下文管理器,with 语句才能工做。下面是一组与上下文管理器和with 语句有关的概念。python
with 语句的语法格式以下:数据库
with context_expression [as target(s)]:
with-body
这里 context_expression 要返回一个上下文管理器对象,该对象并不赋值给 as 子句中的 target(s) ,若是指定了 as 子句的话,会将上下文管理器的 __enter__() 方法的返回值赋值给 target(s)。target(s) 能够是单个变量,或者由“()”括起来的元组。express
Python 对一些内建对象进行改进,加入了对上下文管理器的支持,能够用于 with 语句中,好比能够自动关闭文件、线程锁的自动获取和释放等。假设要对一个文件进行操做,使用 with 语句能够有以下代码:网络
with open(r'somefileName') as somefile: for line in somefile: print line # ...more code
这里使用了 with 语句,无论在处理文件过程当中是否发生异常,都能保证 with 语句执行完毕后已经关闭了打开的文件句柄。若是使用传统的 try/finally 范式,则要使用相似以下代码:函数
somefile = open(r'somefileName') try: for line in somefile: print line # ...more code finally: somefile.close()
比较起来,使用 with 语句能够减小编码量。已经加入对上下文管理协议支持的还有模块 threading、decimal 等。PEP 0343 对with语句的实现进行了描述。with语句的执行过程相似以下代码块:ui
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
开发人员能够自定义支持上下文管理协议的类。自定义的上下文管理器要实现上下文管理协议所须要的 __enter__() 和 __exit__() 两个方法:编码
下面经过一个简单的示例来演示如何构建自定义的上下文管理器。注意,上下文管理器必须同时提供 __enter__() 和 __exit__() 方法的定义,缺乏任何一个都会致使 AttributeError;with 语句会先检查是否提供了 __exit__() 方法,而后检查是否认义了 __enter__() 方法。spa
假设有一个资源 DummyResource,这种资源须要在访问前先分配,使用完后再释放掉;分配操做能够放到 __enter__() 方法中,释放操做能够放到 __exit__() 方法中。简单起见,这里只经过打印语句来代表当前的操做,并无实际的资源分配与释放。线程
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
__exit__() 方法中对变量 exc_tb 进行检测,若是不为 None,表示发生了异常,返回 False 表示须要由外部代码逻辑对异常进行处理;注意到若是没有发生异常,缺省的返回值为 None,在布尔环境中也是被看作 False,可是因为没有异常发生,__exit__() 的三个参数都为 None,上下文管理代码能够检测这种状况,作正常处理。
下面在 with 语句中访问 DummyResource :
1 with DummyResource('Normal'): 2 print '[with-body] Run without exceptions.' 3 4 with DummyResource('With-Exception'): 5 print '[with-body] Run with exception.' 6 raise Exception 7 print '[with-body] Run with exception. Failed to finish statement-body!'
1 Resource [Normal] 2 [Enter Normal]: Allocate resource. 3 [with-body] Run without exceptions. 4 [Exit Normal]: Free resource. 5 [Exit Normal]: Exited without exception.
第2个 with 语句的执行结果以下:
1 Resource [With-Exception] 2 [Enter With-Exception]: Allocate resource. 3 [with-body] Run with exception. 4 [Exit With-Exception]: Free resource. 5 [Exit With-Exception]: Exited with exception raised. 6 7 Traceback (most recent call last): 8 File "G:/demo", line 20, in <module> 9 raise Exception 10 Exception
能够自定义上下文管理器来对软件系统中的资源进行管理,好比数据库链接、共享资源的访问控制等。Python 在线文档 Writing Context Managers 提供了一个针对数据库链接进行管理的上下文管理器的简单范例。
contextlib 模块提供了3个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。使用这些对象,能够对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。
contextmanager 用于对生成器函数进行装饰,生成器函数被装饰之后,返回的是一个上下文管理器,其 __enter__() 和 __exit__() 方法由 contextmanager 负责提供,而再也不是以前的迭代子。被装饰的生成器函数只能产生一个值,不然会致使异常 RuntimeError;产生的值会赋值给 as 子句中的 target,若是使用了 as 子句的话。下面看一个简单的例子。
1 from contextlib import contextmanager 2 3 @contextmanager 4 def demo(): 5 print '[Allocate resources]' 6 print 'Code before yield-statement executes in __enter__' 7 yield '*** contextmanager demo ***' 8 print 'Code after yield-statement executes in __exit__' 9 print '[Free resources]' 10 11 with demo() as value: 12 print 'Assigned Value: %s' % value
结果输出以下:
1 [Allocate resources] 2 Code before yield-statement executes in __enter__ 3 Assigned Value: *** contextmanager demo *** 4 Code after yield-statement executes in __exit__ 5 [Free resources]
能够看到,生成器函数中 yield 以前的语句在 __enter__() 方法中执行,yield 以后的语句在 __exit__() 中执行,而 yield 产生的值赋给了 as 子句中的 value 变量。
须要注意的是,contextmanager 只是省略了 __enter__() / __exit__() 的编写,但并不负责实现资源的“获取”和“清理”工做;“获取”操做须要定义在 yield 语句以前,“清理”操做须要定义 yield 语句以后,这样 with 语句在执行 __enter__() / __exit__() 方法时会执行这些语句以获取/释放资源,即生成器函数中须要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。
nested 能够将多个上下文管理器组织在一块儿,避免使用嵌套 with 语句。
1 with nested(A(), B(), C()) as (X, Y, Z): 2 # with-body code here
相似于:
with A() as X: with B() as Y: with C() as Z: # with-body code here
须要注意的是,发生异常后,若是某个上下文管理器的 __exit__() 方法对异常处理返回 False,则更外层的上下文管理器不会监测到异常。
closing 的实现以下:
class closing(object): # help doc here def __init__(self, thing): self.thing = thing def __enter__(self): return self.thing def __exit__(self, *exc_info): self.thing.close()
上下文管理器会将包装的对象赋值给 as 子句的 target 变量,同时保证打开的对象在 with-body 执行完后会关闭掉。closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,不然执行时会报 AttributeError 错误。
1 class ClosingDemo(object): 2 def __init__(self): 3 self.acquire() 4 def acquire(self): 5 print 'Acquire resources.' 6 def free(self): 7 print 'Clean up any resources acquired.' 8 def close(self): 9 self.free() 10 11 with closing(ClosingDemo()): 12 print 'Using resources'
结果输出以下:
1 Acquire resources. 2 Using resources 3 Clean up any resources acquired.
closing 适用于提供了 close() 实现的对象,好比网络链接、数据库链接等,也能够在自定义类时经过接口 close() 来执行所须要的资源“清理”工做。
再看个例子:
1 from contextlib import contextmanager 2 3 @contextmanager 4 def transaction(db): 5 db.begin() 6 try: 7 yield db 8 except: 9 db.rollback() 10 raise 11 else: 12 db.commit() 13
第一眼上看去,这种实现方式更为简单,可是其机制更为复杂。看一下其执行过程吧:
Python解释器识别到yield
关键字后,def
会建立一个生成器函数替代常规的函数(在类定义以外我喜欢用函数代替方法)。
装饰器contextmanager
被调用并返回一个帮助方法,这个帮助函数在被调用后会生成一个GeneratorContextManager
实例。最终with
表达式中的EXPR
调用的是由contentmanager
装饰器返回的帮助函数。
with
表达式调用transaction(db)
,其实是调用帮助函数。帮助函数调用生成器函数,生成器函数建立一个生成器。
帮助函数将这个生成器传递给GeneratorContextManager
,并建立一个GeneratorContextManager
的实例对象做为上下文管理器。
with
表达式调用实例对象的上下文管理器的__enter()__
方法。
__enter()__
方法中会调用这个生成器的next()
方法。这时候,生成器方法会执行到yield db
处中止,并将db
做为next()
的返回值。若是有as VAR
,那么它将会被赋值给VAR
。
with
中的BLOCK
被执行。
BLOCK
执行结束后,调用上下文管理器的__exit()__
方法。__exit()__
方法会再次调用生成器的next()
方法。若是发生StopIteration
异常,则pass
。
若是没有发生异常生成器方法将会执行db.commit()
,不然会执行db.rollback()
。
本文对 with 语句的语法和工做机理进行了介绍,并经过示例介绍了如何实现自定义的上下文管理器,最后介绍了如何使用 contextlib 模块来简化上下文管理器的编写。
参考:
一、简书:乐乐乐乐乐正
连接:https://www.jianshu.com/p/4aaa570616bf
二、IBM developerworks
链接:https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/