Python 异常处理 (三)

来源:互联网 发布:日本留学中介知乎 编辑:程序博客网 时间:2024/06/15 08:37

  上一篇(Python 异常处理 (二))中,我们了解了如何使用traceback模块和logging模块获取异常信息。这一篇,我们将讲述有关于with,assert,raise的相关知识。


5.with

  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到底是如何工作的呢?下面我们来作一个详细的解释。

5.1.with如何工作

  with语句操作的对象只能是上下文管理器,要使用 with 语句,首先要明白上下文管理器这一概念。故而,我们来解释几个术语:

  • 上下文管理协议(Context Management Protocol):包含方法 __enter__()__exit__()
  • 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()__exit__()方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。
  • 运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__()__exit__()方法实现,__enter__()方法在语句体执行之前进入运行时上下文,__exit__()在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。
  • 上下文表达式(context expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。
  • 语句体(with-body):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_expressionexit = 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()):            raisefinally:    # 正常退出,或者通过 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/。其中有一个非常详细的例子,结合例子来看这里的解释,会理解的更加透彻。

5.2.自定义上下文管理器

  开发人员可以自定义支持上下文管理协议的类,来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。自定义的上下文管理器要实现上下文管理协议所需要的__enter__()__exit__()两个方法:

  • context_manager.__enter__():进入上下文管理器的运行时上下文,在语句体执行前调用。with 语句将该方法的返回值赋值给 as 子句中的 target,如果指定了 as 子句的话(否则返回值会被扔掉);
  • context_manager.__exit__(exc_type, exc_value, exc_traceback):退出与上下文管理器相关的运行时上下文,返回一个布尔值表示是否对发生的异常进行处理。参数表示引起退出操作的异常,如果退出时没有发生异常,则3个参数都为None。如果发生异常,返回True 表示不处理异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。

  下面通过一个简单的示例来演示如何构建自定义的上下文管理器。注意,上下文管理器必须同时提供 __enter__()__exit__()方法的定义,缺少任何一个都会导致 AttributeError;with 语句会先检查是否提供了__exit__()方法,然后检查是否定义了__enter__()方法。

#!/usr/bin/python#coding:utf8import sysreload(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.1with DummyResource('Normal'):        print '[with-body] Run without exceptions.'print "----------**----------"#with-ex.2with 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 ExceptionException

第一个with中,正常执行时会先执行完语句体with-body,然后执行 __exit__()方法释放资源;第二个with中,with-body中发生异常时with-body并没有执行完,但资源会保证被释放掉,同时产生的异常由with语句之外的代码逻辑来捕获处理。

6.assert

  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'>

7.raise

  如果我们想要在自己编写的程序中主动抛出异常,该怎么办呢?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 exceptionoutter 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
以上是本系列的全部参考文献,对原作者表示感谢。

1 0
原创粉丝点击