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,但是不能兩個都有。


請看關於此文件… 裡面有關如何給我們建議的說明。