Python 异常处理 (一)

时间:2021-08-31 23:57:01

  最近在使用Python的过程中,经常需要考虑在代码中加入异常处理的部分,故而特意抽了一些时间仔细查看了相关资料。如今写下这篇博客作为学习笔记,方便以后随时查阅。


1.什么是异常?

  异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。
  Python作为一种面向对象的编程语言,其所抛出的异常也都是对象。Python中常见的标准异常如下:

异常名称 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
GeneratorExit 生成器(generator)发生异常来通知退出
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
StandardError 所有的内建标准异常的基类
ZeroDivisionError 除(或取模)零 (所有数据类型)


这些异常类全部定义于Python的exceptions模块中,Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。它们之间的部分继承关系如下:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError

关于这部分,更详细的内容可前往阅读官方文档:
https://docs.python.org/2.7/library/exceptions.html

2.捕获异常

  异常处理在任何一门编程语言里都是值得关注的一个话题,良好的异常处理可以让你的程序更加健壮,清晰的错误信息更能帮助你快速修复问题。
  首先地,我们来看一段Python中较为完整的捕获异常的代码:

try:
    try_suite
except (Exception1,Exception2,...), Argument:
    exception_suite
......   #other exception block
else:
    no_exceptions_detected_suite
finally:
    always_execute_suite

关于上面这段代码,我们需要注意以下几点:

1. except语句不是必须的(else语句必须和except语句搭配使用,不能单独出现),finally语句也不是必须的,但是二者必须要有一个,否则就没有try的意义了。
2. except语句可以有多个,Python会按except语句的顺序依次匹配你指定的异常,如果异常已经处理就不会再进入后面的except语句。
3. except语句可以以元组形式同时指定多个异常。
4. except语句后面如果不指定异常类型,则默认捕获所有异常,你可以通过logging或者sys模块获取当前异常。(不建议在不清楚逻辑的情况下捕获所有异常,有可能你隐藏了很严重的问题)
5. 如果要捕获异常后要重复抛出,请使用raise,后面不要带任何参数或信息。
6. 不建议捕获并抛出同一个异常,请考虑重构你的代码。
7. 尽量使用内置的异常处理语句来替换try/except语句,比如with语句,getattr()方法。
8. 抛出的异常应该说明原因,有时候你知道异常类型也猜不出所以然的。

以上几点,后面我们还会讲到。接下来,我们首先对这段代码作一个详细的解释。

2.1.try…except…else…finally…语句

  try_suite是需要捕获异常的代码,若try捕获到异常,则交给except处理,否则执行else后的语句,而无论是否捕获到异常,finally后的语句都会被执行。
  更进一步地,try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常:

  • 如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。
  • 如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)。
  • 如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。
  • 无论try后的语句中是否有异常,在通过except(或else)后,finally子句都将被执行。

  来看几个简单的例子:

#!/usr/bin/python
#coding:utf8
a=1/0
print "next sentence"
......output......
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    a=1/0
ZeroDivisionError: integer division or modulo by zero

很明显,上例中,由于除0,抛出了ZeroDivisionError异常,因为我们没有作任何的异常捕获操作,所以程序在执行完第3行之后就终止了,并打印了出错信息,第3行往后的代码将不被执行。下面我们试着来捕获这个异常:

#!/usr/bin/python
#coding:utf8
try:
    a=1/0
    print "next sentence"
except:
    print "Error"
else:
    print "ok"
finally:
    print "finish"
print "out of try"
......output......
Error
finish
out of try

我们成功地捕获了异常,程序没有被终止,在相继地执行了except、finally对应的语句后,程序从“异常捕获”代码块中跳了出来,也就是说若try中存在异常,则当异常被处理后,try语句中异常处往后的代码也不会被执行。
现在我们试着去掉except语句(else语句也必须去掉,不能单独出现,否则会报语法错误),只保留finally语句:

#!/usr/bin/python
#coding:utf8
try:
    a=1/0
    print "next sentence"
#except:
# print "Error"
#else:
# print "ok"
finally:
    print "finish"
print "out of try"
......output......
finish
Traceback (most recent call last):
  File "test.py", line 4, in <module>
    a=1/0
ZeroDivisionError: integer division or modulo by zero

try中抛出了异常,但是并没有被捕获,程序终止,不过在终止之前,finally中的语句被执行了。在这种try/finally句式中,无论有无异常,finally中的代码都要被执行,在某些情况下这是很有用的,比如文件的关闭等。

2.2.异常的参数

  上一节,在我们举的例子中,except后没有带任何异常类型,这种情况下,except会捕获所有的异常,但这并不是一个很好的方法,因为这意味着你并不了解你的代码可能会抛出怎样的异常,而如果将所有的异常都吞掉的话,往往会隐藏一些问题。通常情况下,我们只捕获特定的异常,要做到这一点并不难,在except后加上特定异常的名称即可:

#!/usr/bin/python
#coding:utf8
try:
    a=1/0
except ZeroDivisionError:
    print "Error"
......output......
Error

显然,ZeroDivisionError异常被捕获了。由于只捕获特定异常,因此下面的例子中,异常捕获会失败:

#!/usr/bin/python
#coding:utf8
try:
    a=1/0
except IOError:
    print "Error"
......output......
Traceback (most recent call last):
  File "test.py", line 4, in <module>
    a=1/0
ZeroDivisionError: integer division or modulo by zero

但是,如果except后面跟的异常类型,是所要捕获异常的父类,也是可以的:

#!/usr/bin/python
#coding:utf8
try:
    a=1/0
except Exception:
    print "Error"
......output......
Error

如果想要捕获多个异常的话,可以采用多个except语句,也可以以元组的形式给except语句传递多个异常类型:

#!/usr/bin/python
#coding:utf8
try:
    a=1/0
except ZeroDivisionError:
    print "Error1"
except IOError:
    print "Error2"
......output......
Error1

或者:

#!/usr/bin/python
#coding:utf8
try:
    f=open("1.txt","r")
except (ZeroDivisionError,IOError):
    print "Error"
......output......
Error


  在第二章开头部分的代码中,except后除了跟异常类型,还跟了一个Argument,其实这就是异常的参数。这个参数本质上来讲是所捕获异常类型的一个实例化对象,包含了来自异常代码的诊断信息。也就是说,如果你捕获了一个异常,你就可以对这个异常对象做一些操作,并由此来获取更多关于这个异常的信息。

  下面我们通过几个例子来看一看:

>>> try:
...     a=1/0
... except ZeroDivisionError,e:
...     pass
... 
>>> e
ZeroDivisionError('integer division or modulo by zero',)
>>> type(e)
<type 'exceptions.ZeroDivisionError'>
>>> print e
integer division or modulo by zero
>>> dir(e)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', 'args', 'message']
>>> e.__class__
<type 'exceptions.ZeroDivisionError'>
>>> e.__class__.__name__
'ZeroDivisionError'
>>> e.message
'integer division or modulo by zero'
>>> e.args
('integer division or modulo by zero',)

上面这个例子验证了我们的想法,异常的参数就是所捕获异常的一个对象。注意,当我们直接对对象e使用print方法时,打印出来的是e的message属性,这应该是定义在了这个类的__str__方法中(具体可查阅源码)。另一方面,代码中的“except ZeroDivisionError,e”也可以用“except ZeroDivisionError as e”来代替,而事实上,前者是Python2中的写法,在Python3中已被抛弃,所以建议读者使用后者,并且后者看起来也要更直观一些。

注意,之前我们说过,except后面也可以跟所要捕获异常的父类异常名,在这种情况下,异常的参数到底是谁的对象呢:

>>> try:
...     a=1/0
... except BaseException as e:
...     pass
... 
>>> e
ZeroDivisionError('integer division or modulo by zero',)

可见,e仍然是所要捕获异常的一个对象。

此外,若except后跟多个异常类型,情况又如何:

>>> try:
...     a=1/0
... except (BaseException,IOError) as e: 
...     pass 
... 
>>> e
ZeroDivisionError('integer division or modulo by zero',)

结果是一样的。


  本文中,我们讲了一些关于异常的基本知识。善于思考的读者可能已经发现了,到目前为止,我们只能记录关于异常的部分信息,比如异常的类型和异常的描述,尚缺少关于异常的定位信息。那么,如何才能完整地记录程序发生异常时的出错信息,关于这一点,我们将在下一篇(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
以上是本系列的全部参考文献,对原作者表示感谢。