Python的异常处理机制

  在Python的世界里,一共有两种错误:语法错误(syntax errors)异常(exceptions)

语法错误(syntax errors)

  语法错误,又称解析错误。它发生在编译器编译的时候。

SyntaxError: invalid syntax

异常(exceptions)

  当编译器将Python源代码编译成字节码(bytecode)后,虚拟机会以解释的方式运行字节码。即使编译成功了(没有解析错误),在执行字节码的过程中,也会出现异常。如以下代码:

i = 1/0

  如图所示,当执行到代码i = 1/0的时候,抛出异常ZeroDivisionError,异常信息为:division by zero。同时,在Traceback中输出了异常发生的位置。在Python中,常见的异常,及其之间的关系,如下图所示:

  为了清楚的看到异常的追踪信息,我们写如下代码:

def f():
    g()
def g():
    h()
def h():
    i = 1/0
f()

  我们看到Traceback信息是按照函数调用的过程进行异常提示的。我们知道Python虚拟机在执行代码的时候,为每一个PyCodeObject建立了对应的栈帧PyFrameObject。上述代码至少建立了四个PyCodeObject以及与之对应的PyFrameObject。我们知道栈帧通过f_back指针链接成一个单链表,既然如此,Traceback也是和PyFrameObject一一对应,并且也是一个单向链表。所以,当函数h()对应的运行栈帧出现错误,Traceback会沿着栈帧指针f_back顺藤摸瓜,一直找到代码最开始调用的函数f()。所以,Traceback给出的信息是一个有序的链表信息。具体异常机制,可以参看《Python源码剖析》10.4 Python虚拟机异常控制流。

try语法

  在Python中,我们可以使用try组合语法:

  1. 检测代码异常;
  2. 捕获异常,处理异常;

  我们以打开文件为例来进行说明,最简单的实现我们可以这样写:

filePath = 'test.txt'
f = open(filePath, 'r')
print(f.read())
f.close()

  当文件不存在的时候,虚拟机会直接抛出异常,中断程序的执行。我们希望可以捕获该异常,并对其做处理。这时,我们就需要try,最简单的方式是将所有的代码一股脑放进try子块中:

try:
    filePath = 'test.txt'
    f = open(filePath, 'r')
    print(f.read())
    f.close()
except FileNotFoundError:
    print('file not found!!!')

  这样写,可以吗?可以,但并不好。因为try语句主要目的是为了针对特定的代码块,处理其异常。所有,我们将上面代码进行改写,既然我们只是为了捕获FileNotFoundError异常,我们只将文件打开的代码放在try子块中,而把剩余的代码放在else子句中:

try:
    filePath = 'test.txt'
    f = open(filePath, 'r')
except FileNotFoundError:
    print('file not found!!!')
else:
    print(f.read())
    f.close()

  上述代码执行过程为:

  1. 执行try子句;
  2. 若发生异常,执行except子句;
  3. 若未发生异常,执行else子句;

try子句中存放的是我们专心致志想捕获异常的代码,而没有任何其他干扰。这个代码看似已经很完善了,但是,却存在问题。假设我们现在想要扩充try子句,同时捕获所有异常,我们可以这样写:

try:
    filePath = 'test.txt'
    f = open(filePath, 'r')
    # do ......
except :    # get all exceptions
    print('exceptions!!!')
else:
    print(f.read())
    f.close()

执行过程还是先执行try子句,若有异常,执行except子句,反之,执行else子句。看似完美,但是,当f = open(filePath,'r')执行成功后出现异常,我们并没有将打开的文件句柄关闭。这样就用到了finally:

import sys
try:
    filePath = 'test.txt'
    f = open(filePath, 'r')
except FileNotFoundError:
    exc_type, exc_val, exc_tb = sys.exc_info()
    print('exc_type is :', exc_type)
    print('exc_val is  :', exc_val)
    print('exc_tb is   :', exc_tb)
else:
    print(f.read())
finally:
    f.close()

关于finally

  1. 其功能是清理:关闭资源句柄等;
  2. 不论是否发生异常,finally子句肯定都会执行;
  3. 当遇到return,break,continue时,finally子句依然执行;
try:
    i = 1/0
except:
    print("this is except block")
finally:
    print("this is finally block")

输出结果为:

this is except block
this is finally block
def f():
    try:
        return 1
    finally:
        print("this is finally")
        return 2
f()

输出结果为:

this is finally
2

小结

  本文主要讲述了Python中的异常概念,以及如何使用try组合语句。