赞
踩
上一篇(Python 异常处理 (二))中,我们了解了如何使用traceback模块和logging模块获取异常信息。这一篇,我们将讲述有关于with,assert,raise的相关知识。
with 语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要通过 from __future__ import with_statement
导入后才可以使用),从 2.6 版本开始缺省可用。with 语句作为 try/finally 编码范式的一种替代,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
上面的这段解释略显生硬,不如我们先来对比着看两个例子,体会一下with的简单用法,然后再来进行深入讲解:
>>> try:
... fout = open("test.txt", "w")
... fout.write("this is a test")
... finally:
... fout.close()
>>> with open("test.txt", "w") as fout:
... fout.write("this is a test")
以上两段代码要做的事情是完全一致的。在第一个例子中,我们打开了一个文件,为了确保在操作文件的过程中无论是否发生异常,文件都能被关闭,我们采用了try/finally句式;而在第二个例子中,我们用with/as替代了try/finally,这种句式同样能够做到对文件的及时关闭。比较起来,使用 with 语句可以减少编码量,显得更加优雅,所以我们建议使用后者。
那么with到底是如何工作的呢?下面我们来作一个详细的解释。
with语句操作的对象只能是上下文管理器,要使用 with 语句,首先要明白上下文管理器这一概念。故而,我们来解释几个术语:
__enter__()
和 __exit__()
。__enter__()
和 __exit__()
方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。__enter__()
和__exit__()
方法实现,__enter__()
方法在语句体执行之前进入运行时上下文,__exit__()
在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。__enter__()
方法,执行完语句体之后会执行__exit__()
方法。with语句的语法格式如下:
with context_expression [as target(s)]:
with-body
这里 context_expression 要返回一个上下文管理器对象,该对象并不赋值给 as 子句中的 target(s) ,如果指定了 as 子句的话,会将上下文管理器的__enter__()
方法的返回值赋值给 target(s)。target(s) 可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。
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
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执行过程的阐述可能略显生硬,建议读者参考:http://blog.csdn.net/suwei19870312/article/details/23258495/。其中有一个非常详细的例子,结合例子来看这里的解释,会理解的更加透彻。
开发人员可以自定义支持上下文管理协议的类,来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。自定义的上下文管理器要实现上下文管理协议所需要的__enter__()
和__exit__()
两个方法:
下面通过一个简单的示例来演示如何构建自定义的上下文管理器。注意,上下文管理器必须同时提供 __enter__()
和__exit__()
方法的定义,缺少任何一个都会导致 AttributeError;with 语句会先检查是否提供了__exit__()
方法,然后检查是否定义了__enter__()
方法。
#!/usr/bin/python
#coding:utf8
import sys
reload(sys)
sys.setdefaultencoding("utf8")
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
#with-ex.1
with DummyResource('Normal'):
print '[with-body] Run without exceptions.'
print "----------**----------"
#with-ex.2
with DummyResource('With-Exception'):
print '[with-body] Run with exception.'
raise Exception
print '[with-body] Run with exception. Failed to finish statement-body!'
DummyResource中的__enter__()
返回的是自身的引用,这个引用可以赋值给as子句中的 target 变量,这样target本身就是一个上下文管理器对象;返回值的类型可以根据实际需要设置为不同的类型,不必是上下文管理器对象本身。执行结果如下:
Resource [Normal]
[Enter Normal]: Allocate resource.
[with-body] Run without exceptions.
[Exit Normal]: Free resource.
[Exit Normal]: Exited without exception.
----------**----------
Resource [With-Exception]
[Enter With-Exception]: Allocate resource.
[with-body] Run with exception.
[Exit With-Exception]: Free resource.
[Exit With-Exception]: Exited with exception raised.
Traceback (most recent call last):
File "test_with.py", line 32, in <module>
raise Exception
Exception
第一个with中,正常执行时会先执行完语句体with-body,然后执行 __exit__()
方法释放资源;第二个with中,with-body中发生异常时with-body并没有执行完,但资源会保证被释放掉,同时产生的异常由with语句之外的代码逻辑来捕获处理。
assert,即断言,直接看语法:
assert expression [,arguments]
其中assert是断言的关键字。执行该语句的时候,先判断表达式expression,如果表达式为真,则什么都不做;如果表达式不为真,则抛出AssertionError异常,传进去的字符串arguments则会作为异常类的实例的具体信息存在。来看一个例子:
>>> try:
... assert 1==2,"1 is not equal 2!"
... except AssertionError as e:
... print e
...
1 is not equal 2!
>>> type(e)
<type 'exceptions.AssertionError'>
如果我们想要在自己编写的程序中主动抛出异常,该怎么办呢?raise语句可以帮助我们达到目的。其基本语法如下:
raise [SomeException [, args [,traceback]]
第一个参数,SomeException必须是一个异常类,或异常类的实例;第二个参数是传递给SomeException的参数,必须是一个元组,这个参数用来传递关于这个异常的有用信息;第三个参数traceback很少用,是一个traceback对象。
来看两个例子:
>>> try:
... raise NameError("this is a test")
... except NameError as e:
... print e
...
this is a test
>>> type(e)
<type 'exceptions.NameError'>
>>> try:
... raise NameError,"this is a test"
... except NameError as e:
... print e
...
this is a test
>>> type(e)
<type 'exceptions.NameError'>
以上两个例子的结果是一致的,但是传入的参数不同:前者传入的是一个异常类的对象,后者传入的是异常类+参数args。
还有一种情况,叫做传递异常(re-raise Exception),即捕捉到了异常,但是又想重新引发它,我们先来看个例子:
>>> try:
... try:
... raise IOError
... except IOError:
... print "inner exception"
... raise
... except IOError:
... print "outter exception"
...
inner exception
outter exception
这个是将拦截到的异常错误原样抛出扔给上层处理,处理过程是:首先被内层IOError异常捕获,打印“inner exception”, 然后把相同的异常再抛出,被外层的except捕获,打印”outter exception”。在Python2中,为了保持异常的完整信息,那么你捕获后再次抛出时千万不能在raise后面加上异常对象,否则你的trace信息就会从此处截断。以上是最简单的重新抛出异常的做法。
还有一些技巧可以考虑,比如抛出异常前对异常的信息进行更新:
>>> try:
... try:
... a=1/0
... except ZeroDivisionError as e:
... print e
... e.args += ('more info',)
... print e
... raise
... except ZeroDivisionError as e:
... print e
...
integer division or modulo by zero
('integer division or modulo by zero', 'more info')
('integer division or modulo by zero', 'more info')
本文中我们讲了关于with,assert,raise的知识,下一篇(Python 异常处理 (四))我们将介绍关于用户自定义异常的相关内容。
[1] http://www.runoob.com/python/python-exceptions.html
[2] http://blog.csdn.net/sinchb/article/details/8392827
[3] https://segmentfault.com/a/1190000007736783
[4] http://www.tuicool.com/articles/f2uumm
[5] https://docs.python.org/2.7/library/sys.html
[6] http://www.2cto.com/kf/201303/194676.html
[7] http://python.usyiyi.cn/python_278/library/sys.html
[8] https://docs.python.org/2.7/library/traceback.html
[9] https://docs.python.org/2.7/library/exceptions.html
[10] http://blog.csdn.net/liuxiaochen123/article/details/48155995
[11] https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/
[12] http://blog.csdn.net/suwei19870312/article/details/23258495/
[13] https://docs.python.org/release/2.6/whatsnew/2.6.html
[14] http://python3-cookbook.readthedocs.io/zh_CN/latest/c14/p08_creating_custom_exceptions.html
[15] https://docs.python.org/2.7/tutorial/errors.html
以上是本系列的全部参考文献,对原作者表示感谢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。