标签:
在使用Python编程中,可以会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作;当语句块执行完成后,需要继续执行一些收尾动作。
例如:当需要操作文件或数据库的时候,首先需要获取文件句柄或者数据库连接对象,当执行完相应的操作后,需要执行释放文件句柄或者关闭数据库连接的动作。
又如,当多线程程序需要访问临界资源的时候,线程首先需要获取互斥锁,当执行完成并准备退出临界区的时候,需要释放互斥锁。
对于这些情况,Python中提供了上下文管理器(Context Manager)的概念,可以通过上下文管理器来定义/控制代码块执行前的准备动作,以及执行后的收尾动作。
那么在Python中怎么实现一个上下文管理器呢?这里,又要提到两个"魔术方法",__enter__和__exit__,下面就是关于这两个方法的具体介绍。
也就是说,当我们需要创建一个上下文管理器类型的时候,就需要实现__enter__和__exit__方法,这对方法就称为上下文管理协议(Context Manager Protocol),定义了一种运行时上下文环境。
在Python中,可以通过with语句来方便的使用上下文管理器,with语句可以在代码块运行前进入一个运行时上下文(执行__enter__方法),并在代码块结束后退出该上下文(执行__exit__方法)。
with语句的语法如下:
with context_expr [as var]:
with_suite
在Python的内置类型中,很多类型都是支持上下文管理协议的,例如file,thread.LockType,threading.Lock等等。这里我们就以file类型为例,看看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 time class MyTimer(object): def __init__(self, verbose = False): self.verbose = verbose def __enter__(self): self.start = time.time() return self def __exit__(self, *unused): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 if self.verbose: print "elapsed time: %f ms" %self.msecs
下面结合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)
代码输出结果为:
在使用上下文管理器中,如果代码块 (with_suite)产生了异常,__exit__方法将被调用,而__exit__方法又会有不同的异常处理方式。
当__exit__方法退出当前运行时上下文时,会并返回一个布尔值,该布尔值表明了"如果代码块 (with_suite)执行中产生了异常,该异常是否须要被忽略"。
1. __exit__返回False,重新抛出(re-raised)异常到上层
修改前面的例子,在MyTimer类型中加入了一个参数"ignoreException"来表示上下文管理器是否会忽略代码块 (with_suite)中产生的异常。
import time class MyTimer(object): def __init__(self, verbose = False, ignoreException = False): self.verbose = verbose self.ignoreException = ignoreException def __enter__(self): self.start = time.time() return self def __exit__(self, *unused): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 if self.verbose: print "elapsed time: %f ms" %self.msecs return self.ignoreException try: 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__函数如下:
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.msecs print "exception_type: ", exception_type print "exception_value: ", exception_value print "traceback: ", traceback return self.ignoreException
这次运行结果中,就显示出了更多异常相关的信息了:
本文介绍了Python中的上下文管理器,以及如何结合with语句来使用上下文管理器。
总结一下with 语句的执行流程:
在很多情况下,with语句可以简化代码,并增加代码的健壮性。
标签:
原文地址:http://www.cnblogs.com/wilber2013/p/4638967.html