错误处理(error handling)和异常
如果在程序运行中发生了错误,Python 解释器将输出错误信息和对执行栈的追踪信息。

在交互式执行的模式下,显示错误信息后,解释器返回到基本提示符状态。

在 IDLE 里,如果是执行正在编辑的文件里的程序,解释器输出信息后回到交互状态。

如果执行的程序来自文件,解释器在输出上述信息之后结束本次文件的执行。

在程序执行中输入中断字符(ctrl-C),可以使解释器中断对程序的执行并回到基本提示符状态。(例如遇到死循环,或希望中断正在执行的程序,不希望继续计算)。

异常(exception)

解释器可能检查程序执行中发生的一些错误,这些错误都不是致命,Python 称它们为异常。Python 有特殊的用于处理异常的结构,可以利用这些结构,让程序在完成对异常的处理后回到正常工作状态,继续执行。

Python 还提供了定义异常和引发异常的机制,我们可以根据需要,利用它们描述一些特殊的执行流程。

如果发生了异常,程序里又没有处理,正在执行的程序就会以异常的方式终止,系统输出有关的错误信息。在交互状态下看到的动态运行错误信息就是这种情况。

下面是 Python 教程里的几个例子:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "", line 1, in ?
ZeroDivisionError: int division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly
第一个错误是遇到除数为 0 的情况;第二个遇到变量没定义(因此没有值);第三个是运算对象的类型错,系统无法做字符串和整数的加法(不会自动将整数转换到字符串)。错误信息里出现的几个表示错误情况的名字(ZeroDivisionError, NameError, TypeError)就称为 "异常名",这是一些语言内部定义的异常。

每个异常是一个特殊的类型,标准库手册第5节列出了 Python 系统的所有内部定义异常。

异常处理(exception handling)

如果预见到一段程序的执行中可能发生某些异常,而且存在合适的方式处理这些(或者是其中的一种或几种)异常,那么就可以写处理异常的程序段。

Python 里为处理异常提供的结构是 try 语句。假定原来的可能发生异常的程序段是 code1,可能发生的一个异常的名字是 ex1,处理这个异常的代码段是 code2,可以用下面程序段完成有关的处理流程:

try :
    code1
except ex1 :
    code2
程序执行这段代码执行时,解释器立即去执行 code1。如果 code1 的执行中没出现异常,它的执行完成也就是上述整个代码段的执行完成。如果 code1 执行中发生了异常 ex1,执行立刻转入代码段 code2(对 code1 的执行立刻终止,无论当时执行到 code1 中哪个地方),code2 的执行完成就是这个代码段的执行完成。

上面给出的是最简单的情况。实际上

  • 一个 try 语句可以包含一个 try 段和一个或多个 except 段。
  • 在一个 except 段的关键字 except 后面可以写一个或多个异常名(多个异常名写成元组的形式,名字之间用逗号分隔,最外面加括号,采用元组的描述方式),表示这个 except 段处理在 try 段代码执行中发生的这些异常。except 段中的代码段称为(针对这些异常的)"异常处理器"。try 语句的最后一个 except 段可以不写异常名,表示它处理所有前面 except 段未处理的异常。
  • 执行一个 try 语句就是执行其 try 段的代码。如果这段代码的执行正常完成,该 try 语句的执行也就完成了。位于 try 段之后的所有 except 段都直接跳过,解释器继续执行该 try 语句之后的其他代码。
  • 如果在 try 段代码的执行中发生了一个异常,解释器将顺序地检查该 try 语句的各个 except 段。如果发现某个 except 段列出的异常与发生的异常匹配,就转入执行相应的异常处理器。该处理器的执行完成也就是整个 try 语句的执行完成(位于该 except 段之后的所有 except 段都直接跳过)。执行转到这个 try 语句后面的代码。
  • 执行中发生的异常和 except 段列出的异常之间的匹配,最简单的情况就是名字相同。更准确的情况要在介绍里 Python 的类型结构之后再说明。
  • 如果这个 try 语句的所有 except 段都不能处理发生的异常,或者在处理异常的 except 段的执行中又发生异常,那么:
    • 如果这个 try 语句位于另一 try 语句的 try 段里,就按照有关的异常发生在这个外围的 try 语句里的情况继续处理。这种情况可能一直传到当前函数里最外层的 try 语句。
    • 如果发生异常的 try 语句已经没有外围 try 语句,但它是在一个函数里,那么相应的函数调用以异常的方式终止,该异常在这个函数的调用点再次发生,在该调用语句的外围检查能否处理。如果这个调用出现在一个 try 语句里,就考虑其 except 块。
  • 如果异常发生(或传播)到主程序,且已经没有外围的 try 语句,程序的执行终止并输出相应错误信息。
注意:使用无异常名的 except 块要特别当心,因为它会掩盖发生的异常,可能导致程序产生我们不希望的结果(看下一节的正确处理方式示例)。

异常的引发(raise 语句)

可以在程序的代码里主动引发异常,Python 为引发异常提供了专门的 raise 语句,其简单使用形式是:
	raise 异常名
程序执行到这个语句将导致指定的异常发生。

如果在 try 语句的异常处理器里使用 raise 语句,可以不写 "异常名",表示引发当时捕捉到的且正在处理的那个异常。显然,只有在发生了一个异常时,执行才可能到达与之对应的 except 块的异常处理器。由于一个 except 段可能处理多个异常(例如它处理的异常用用一个元组描述),写不带异常名的 raise 语句可以保证再次引发的仍然是当时捕捉到并正在处理的那个异常。

下面是教程里给出的一个示例:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as err:
    print("I/O error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise
第一个 except 块处理 IOError(与输入输出有关的异常),例如文件不能正常打开等。这是一个内部定义异常,发生这种异常时将触发第一个异常处理器,它输出相应信息后结束。

如果文件正常打开,但读入的一行不能转换为整数,就会引发一个 ValueError 异常,触发第二个异常处理器。

如果在 try 语句基本代码段的执行中发生其他异常,就会触发第三个异常处理器。该处理器输出一条错误信息后再次引发当时发生的异常,把它传播出去。如果上面这段代码是写在一个函数里,该异常就会继续传到调用这个函数的代码段,那里可以对发生的异常做进一步处理。

try 语句的 else 段

在一个 try 语句的所有 except 块之后可以有一个 else 块。如果在这个 try 语句的基本代码段执行中没有出现异常,该代码执行完成后就执行这个 else 段里的语句。

下面是一个例子:

for fname in flist :
    try:
        f = open(fname, 'r')
        usefile(f)
    except IOError:
        print('cannot open', fname)
    else:
        print('File ', fname, ' have been processed.')
        f.close()
这里假定 flist 里是一组表示文件名的字符串,我们需要一个个处理这些文件的内容,函数 usefile 完成对一个文件的内容的处理。如果文件正常打开并处理,程序的执行将进入最后的 else 段,输出一条信息并关闭文件。如果文件不能正常打开,执行中发生异常,异常处理器输出一条说明文件有问题的信息。无论怎样,循环都将继续到处理完 flist 列出的所有名字。

finally 段

try 语句还可以有一个 finally 段,用于描述这个 try 语句结束前必须执行的操作。无论该 try 语句的执行中是否出现异常,是否有异常处理器被执行,在异常处理器的执行中是否出现异常,finally 段里的语句都会执行。

注意:无论一个 try 语句有没有 finally 段,都不影响它的正常或异常终止状态。如果在执行 final 段之前执行处于正常状态,执行正常进入 finally 段,这个段结束时的状态(正常或异常)决定整个 try 语句的结束时的状态。如果 try 语句的执行以异常状态进入 finally 段,这个 try 语句必定以异常状态结束。

人们通常在 finally 段里写一些清理操作,以保证跟在 try 语句后面的代码能正常执行。例子参看教程的8.6节。

有关正确使用异常处理机制的技术和方法,参见课堂讨论和其他参考材料。

本页及相关页面(除另声明者外)由裘宗燕创建维护,可自由用于各种学习活动。其他使用需得到作者许可。