Subsections


8. 程式错误与例外(Exceptions)情形

至此为止,我们都只有稍稍的提到错误讯息。但是如果你有试着执行上面的范例的话,你可能注意到,基本上错误的情况可以分成两类:语法错误 ( syntax errors ) 以及例外情况 ( exceptions )。


8.1 语法错误

语法错误也叫做分析时的错误(parsing errors),大概是一般在学Python时最常见到的直译器所发出来的抱怨:

>>> while 1 print 'Hello world'
File "<stdin>", line 1
while 1 print 'Hello world'
^
SyntaxError: invalid syntax

Python分析器(parser)会在印出错误的行,并且用一个向上的箭号指出最早发现错误的地方,而这个错误是发生(至少是被发现)在这个箭号所指的单元(token) 之前。在我们的例子里面:错误发生在 print 这个关键字,因为前面应该有一个 ( " :" ) 。错误信息里面也包含档案名称以及行数,所以你可以很快知道要到哪里去找错。


8.2 例外(Exceptions)情形

有的时候,甚至当你的语法完全正确时,当你执行程式时仍然会出错。这种在程式执行阶段发生的错误叫做例外情形 ( exceptions ) ,并且会造成程式致命的终止(无法执行下去)。你待会就会知道在Python里面要怎样处理这样的状况,但是我们先来看这样的状况下会造成什么错误信息:

>>> 10 * (1/0)
Traceback (innermost last):
File "<stdin>", line 1
ZeroDivisionError: integer division or modulo
>>> 4 + spam*3
Traceback (innermost last):
File "<stdin>", line 1
NameError: spam
>>> '2' + 2
Traceback (innermost last):
File "<stdin>", line 1
TypeError: illegal argument type for built-in operation

在这些错误信息的最后一行都是解释到底发生了什么事。例外情况(Exception)有很多种类型,类型的名称也在错误信息之中,在上面的例子里面,exception的类型分别是: ZeroDivisionError, NameError 以及 TypeError. 。对于内建的exception来说,这些印出来的字串都是这些内建的exception类型的真正类型名称,但是对于使用者自己自定的exception类型就不一定了(虽然这是一个有用的约定俗成的习惯)。这些标准的exception名称也正是他们内建的指称(identifiers) (不是正式的关键字)。

这一行其他部分则是有关这个exception类型的详细解释,其意义则是依照exception的类型而有不同。

在错误信息最后一行之前的部分则是显示了这个exception发生时的状况,也就是记忆体堆积(stack)的内容追朔(backtrace)。一般来说这个这个部分包含了stack backtrace的来源行数,但是这并不代表是在从标准输入读入时候的行数。

在Python程式库参考手册中( Python Library Reference )详细列出了所有的内建exception及其说明。


8.3 例外(Exceptions)情形的处理

我们可以写一个程式来处理某些的exception。请看下面程式范例,我们要求使用者输入一个有效的数字,直到所输入的数字真正有效为止。但是使用者也可以中止这个程式(用 Control-C 或者是任何作业系统支援的方式)。值得注意的是,使用者主动中止程式事实上是使用者引发一个 KeyboardInterrupt 的exception。

>>> while 1:
... try:
... x = int(raw_input("Please enter a number: "))
... break
... except ValueError:
... print "Oops! That was no valid number. Try again..."
...

这个 try 叙述的作用如下:

一个 try 叙述可以包含许多的except 部分来处理各种不同的exception,但是最多只有一个handler(译:exception之后的叙述)会真正被执行。Handlers 只处理在所对应的 try 部分发生的exception,其他的 try 部分发生的exception则不在处理范围。一个except子句可以处理一个以上的exception,只要用list括弧把它们括起来。例如:

... except (RuntimeError, TypeError, NameError):
... pass

最后的一个 except 可以不写出exception 类型的名称,这就当作是一个外卡(wildcard,译:处理所有的exception)来使用。当使用时要特别的小心,因为如果你很有可能就把一个应该被注意的程式错误给隐藏起来了。你也可以在这个except子句里面印出一个错误信息,然后把这个exception再丢(raise)出去(让呼叫你程式的人来处理这个exception)。

import string, sys

try:
f = open('myfile.txt')
s = f.readline()
i = int(string.strip(s))
except IOError, (errno, strerror):
print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise

这个 try ... except 的叙述有一个可有可无的else子句( else clause )可以使用,当这个子句存在时,必须是放在所有的except clauses的后面。这个子句里的叙述是当try子句没有发生任何exception时,一定要执行的叙述。请看例子:

for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'cannot open', arg
else:
print arg, 'has', len(f.readlines()), 'lines'
f.close()

使用 else 要比在 try 子句里面加入多余的程式码来的好,因为这样减少意外的处理到那些不是由 try ... except 叙述中保护的程式码所引发的exception。

当一个exception 发生时,exception本身有一个 连带的值,也叫做这个exception的参数( argument )。至于这个参数是否存在以及其型态,则是由exception的类型所决定。对于有这个参数存在的exception类型来说,你可以在except clause的后面加入一个名称(或是list)来接收这个参数的值。请看下例:

>>> try:
... spam()
... except NameError, x:
... print 'name', x, 'undefined'
...
name spam undefined

如果一个exception 有一个参数的话,当它在没有被处理,当作错误信息印出来的时候,就会成为最后(详细解释(`detail'))的一部份。

Exception的处理者(handlers,exception clause)并不只处理在try clause当中所发生的exception,也会处理所有在try clause当中所(直接或间接)呼叫的函式所引发的exception。请看下例:

>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError, detail:
... print 'Handling run-time error:', detail
...
Handling run-time error: integer division or modulo


8.4 如何引发例外(Exceptions)

使用 raise 叙述可以引发一个特定的 exception,例如:

>>> raise NameError, 'HiThere'
Traceback (innermost last):
File "<stdin>", line 1
NameError: HiThere

raise 的第一个参数是想要引发的exception的类型,第二个参数(可有可无)则是指定这个exception的参数值。


8.5 使用者自订的例外(Exceptions)

程式设计师可以自己命名自己想要的excetion,其方法是指定一个字串给一个变数,或者是自己创造一个新的exception类别来。举例说明:

>>> class MyError:
... def __init__(self, value):
... self.value = value
... def __str__(self):
... return `self.value`
...
>>> try:
... raise MyError(2*2)
... except MyError, e:
... print 'My exception occurred, value:', e.value
...
My exception occurred, value: 4
>>> raise MyError, 1
Traceback (innermost last):
File "<stdin>", line 1
__main__.MyError: 1

许多标准的module都自己自订其exception来报回(report)在他们自己所定义的函式里面所发生的错误。

有关于classes 更多的讨论请看第九章 ``类别''。


8.6 定义善后的动作

try 叙述的机制里面有一个可有可无的子句(optional clause),其功用是在定义不管什么情况发生下,你都得要做的清除善后的工作。 举例来说:

>>> try:
... raise KeyboardInterrupt
... finally:
... print 'Goodbye, world!'
...
Goodbye, world!
Traceback (innermost last):
File "<stdin>", line 2
KeyboardInterrupt

这个 finally clause 不管你的程式在try里面是否有任何的exception发生都会被执行。当exception发生时,程式会执行finally clause之后再引发这个exception。当程式的 try try部分因为 breakreturn 而离开时,finally clause也一样会在离开的时候(``on the way out'')被执行。

一个 try 叙述机制应该要有一个或多个except clauses,或者是有一个 finally clause,但是不能两个都有。


请看关于此文件… 里面有关如何给我们建议的说明。