上下文管理器
在使用Python编程中,能够会常常碰到这种状况:有一个特殊的语句块,在执行这个语句块以前须要先执行一些准备动做;当语句块执行完成后,须要继续执行一些收尾动做。数据库
例如:当须要操做文件或数据库的时候,首先须要获取文件句柄或者数据库链接对象,当执行完相应的操做后,须要执行释放文件句柄或者关闭数据库链接的动做。编程
又如,当多线程程序须要访问临界资源的时候,线程首先须要获取互斥锁,当执行完成并准备退出临界区的时候,须要释放互斥锁。多线程
对于这些状况,Python中提供了上下文管理器(Context Manager)的概念,能够经过上下文管理器来定义/控制代码块执行前的准备动做,以及执行后的收尾动做。app
上下文管理协议
那么在Python中怎么实现一个上下文管理器呢?这里,又要提到两个"魔术方法",__enter__和__exit__,下面就是关于这两个方法的具体介绍。函数
- __enter__(self) Defines what the context manager should do at the beginning of the block created by the with statement. Note that the return value of __enter__ is bound to the target of the with statement, or the name after the as.
- __exit__(self, exception_type, exception_value, traceback) Defines what the context manager should do after its block has been executed (or terminates). It can be used to handle exceptions, perform cleanup, or do something always done immediately after the action in the block. If the block executes successfully, exception_type, exception_value, and traceback will be None. Otherwise, you can choose to handle the exception or let the user handle it; if you want to handle it, make sure __exit__ returns True after all is said and done. If you don't want the exception to be handled by the context manager, just let it happen.
也就是说,当咱们须要建立一个上下文管理器类型的时候,就须要实现__enter__和__exit__方法,这对方法就称为上下文管理协议(Context Manager Protocol),定义了一种运行时上下文环境。post
with语句
在Python中,能够经过with语句来方便的使用上下文管理器,with语句能够在代码块运行前进入一个运行时上下文(执行__enter__方法),并在代码块结束后退出该上下文(执行__exit__方法)。ui
with语句的语法以下:this
with context_expr [as var]:
with_suite
- context_expr是支持上下文管理协议的对象,也就是上下文管理器对象,负责维护上下文环境
- as var是一个可选部分,经过变量方式保存上下文管理器对象
- with_suite就是须要放在上下文环境中执行的语句块
在Python的内置类型中,不少类型都是支持上下文管理协议的,例如file,thread.LockType,threading.Lock等等。这里咱们就以file类型为例,看看with语句的使用。spa
with语句简化文件操做
当须要写一个文件的时候,通常都会经过下面的方式。代码中使用了try-finally语句块,即便出现异常,也能保证关闭文件句柄。线程
logger = open("log.txt", "w") try: logger.write('Hello ') logger.write('World') finally: logger.close()print logger.closed
其实,Python的内置file类型是支持上下文管理协议的,能够直接经过内建函数dir()来查看file支持的方法和属性:
>>> print dir(file) ['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', ' __getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclass hook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', ' mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines'] >>>
因此,能够经过with语句来简化上面的代码,代码的效果是同样的,可是使用with语句的代码更加的简洁:
with open("log.txt", "w") as logger: logger.write('Hello ') logger.write('World')print logger.closed
自定义上下文管理器
对于自定义的类型,能够经过实现__enter__和__exit__方法来实现上下文管理器。
看下面的代码,代码中定义了一个MyTimer类型,这个上下文管理器能够实现代码块的计时功能:
import timeclass MyTimer(object):
</span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__enter__</span><span style="color: #000000;">(self): self.start </span>=<span style="color: #000000;"> time.time() </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> self </span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__exit__</span>(self, *<span style="color: #000000;">unused): self.end </span>=<span style="color: #000000;"> time.time() self.secs </span>= self.end -<span style="color: #000000;"> self.start self.msecs </span>= self.secs * 1000 <span style="color: #0000ff;">if</span><span style="color: #000000;"> self.verbose: </span><span style="color: #0000ff;">print</span> <span style="color: #800000;">"</span><span style="color: #800000;">elapsed time: %f ms</span><span style="color: #800000;">"</span> %self.msecs</pre>
def init(self, verbose = False):
self.verbose = verbose
下面结合with语句使用这个上下文管理器:
def fib(n): if n in [1, 2]: return 1 else: return fib(n-1) + fib(n-2)with MyTimer(True):
print fib(30)
代码输出结果为:
异常处理和__exit__
在使用上下文管理器中,若是代码块 (with_suite)产生了异常,__exit__方法将被调用,而__exit__方法又会有不一样的异常处理方式。
当__exit__方法退出当前运行时上下文时,会并返回一个布尔值,该布尔值代表了"若是代码块 (with_suite)执行中产生了异常,该异常是否需要被忽略"。
1. __exit__返回False,从新抛出(re-raised)异常到上层
修改前面的例子,在MyTimer类型中加入了一个参数"ignoreException"来表示上下文管理器是否会忽略代码块 (with_suite)中产生的异常。
import timeclass MyTimer(object):
def init(self, verbose = False, ignoreException = False):
self.verbose = verbose
self.ignoreException = ignoreExceptiontry:
</span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__enter__</span><span style="color: #000000;">(self): self.start </span>=<span style="color: #000000;"> time.time() </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> self </span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__exit__</span>(self, *<span style="color: #000000;">unused): self.end </span>=<span style="color: #000000;"> time.time() self.secs </span>= self.end -<span style="color: #000000;"> self.start self.msecs </span>= self.secs * 1000 <span style="color: #0000ff;">if</span><span style="color: #000000;"> self.verbose: </span><span style="color: #0000ff;">print</span> <span style="color: #800000;">"</span><span style="color: #800000;">elapsed time: %f ms</span><span style="color: #800000;">"</span> %<span style="color: #000000;">self.msecs </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> self.ignoreException
with MyTimer(True, False):
raise Exception("Ex4Test")
except Exception, e:
print "Exception (%s) was caught" %e
else:
print "No Exception happened"
运行这段代码,会获得如下结果,因为__exit__方法返回False,因此代码块 (with_suite)中的异常会被继续抛到上层代码。
2. __exit__返回Ture,代码块 (with_suite)中的异常被忽略
将代码改成__exit__返回为True的状况:
try: with MyTimer(True, True): raise Exception("Ex4Test") except Exception, e: print "Exception (%s) was caught" %e else: print "No Exception happened"
运行结果就变成下面的状况,代码块 (with_suite)中的异常被忽略了,代码继续运行:
必定要当心使用__exit__返回Ture的状况,除非很清楚为何这么作。
3. 经过__exit__函数完整的签名获取更多异常信息
对于__exit__函数,它的完整签名以下,也就是说经过这个函数能够得到更多异常相关的信息。
- __exit__(self, exception_type, exception_value, traceback)
继续修改上面例子中的__exit__函数以下:
def __exit__(self, exception_type, exception_value, traceback): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 if self.verbose: print "elapsed time: %f ms" %self.msecsreturn self.ignoreException
</span><span style="color: #0000ff;">print</span> <span style="color: #800000;">"</span><span style="color: #800000;">exception_type: </span><span style="color: #800000;">"</span><span style="color: #000000;">, exception_type </span><span style="color: #0000ff;">print</span> <span style="color: #800000;">"</span><span style="color: #800000;">exception_value: </span><span style="color: #800000;">"</span><span style="color: #000000;">, exception_value </span><span style="color: #0000ff;">print</span> <span style="color: #800000;">"</span><span style="color: #800000;">traceback: </span><span style="color: #800000;">"</span><span style="color: #000000;">, traceback
此次运行结果中,就显示出了更多异常相关的信息了:
总结
本文介绍了Python中的上下文管理器,以及如何结合with语句来使用上下文管理器。
总结一下with 语句的执行流程:
- 执行context_expr 以获取上下文管理器对象
-
调用上下文管理器的 __enter__() 方法
- 若是有 as var 从句,则将 __enter__() 方法的返回值赋给 var
- 执行代码块 with_suite
-
调用上下文管理器的 __exit__() 方法,若是 with_suite 产生异常,那么该异常的 type、value 和 traceback 会做为参数传给 __exit__(),不然传三个 None
- 若是 with_suite 产生异常,而且 __exit__() 的返回值等于 False,那么这个异常将被从新抛出到上层
- 若是 with_suite 产生异常,兵器 __exit__() 的返回值等于 True,那么这个异常就被忽略,继续执行后面的代码
在不少状况下,with语句能够简化代码,并增长代码的健壮性。