8. 錯誤和例外¶
到目前為止還沒有提到錯誤訊息,但如果你嘗試運行範例,你可能會發現一些錯誤訊息。常見的(至少)兩種不同的錯誤類別為:語法錯誤 (syntax error) 和例外 (exception)。
8.1. 語法錯誤 (Syntax Error)¶
語法錯誤又稱剖析錯誤 (parsing error),它或許是學習 Python 的過程最常聽見的抱怨:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^^^^^
SyntaxError: invalid syntax
剖析器 (parser) 會重複犯錯的那一行,並用一個小箭頭指向該行檢測到錯誤的地方,但請注意這並非都會是應該去修改的地方。此例中,錯誤是在 print() 函式中被檢測到,因為在它前面少了一個冒號 (':')。
檔案名稱(此例中為 <stdin>)和列號會被印出來,所以如果訊息是來自一個檔案時,就可以知道去哪裡找問題。
8.2. 例外 (Exception)¶
即使一段陳述式或運算式使用了正確的語法,嘗試執行時仍可能導致錯誤。執行時檢測到的錯誤稱為例外,例外不一定都很嚴重:你很快就能學會在 Python 程式中如何處理它們。不過大多數的例外不會被程式處理,並且會顯示如下的錯誤訊息:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
10 * (1/0)
~^~
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
4 + spam*3
^^^^
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
'2' + 2
~~~~^~~
TypeError: can only concatenate str (not "int") to str
錯誤訊息的最後一行指示發生了什麼事。例外有不同的類型,而類型名稱會作為訊息的一部份被印出。範例中的例外類型為:ZeroDivisionError、NameError 和 TypeError。作為例外類型被印出的字串,就是發生的內建例外 (built-in exception) 的名稱。所有的內建例外都是如此運作,但對於使用者自定的例外則不一定需要遵守(雖然這是一個有用的慣例)。標準例外名稱是內建的識別字 (identifier),不是保留關鍵字 (reserved keyword)。
此行其餘部分,根據例外的類型及導致例外的原因,說明例外的細節。
錯誤訊息的開頭,用堆疊回溯 (stack traceback) 的形式顯示發生例外的語境。一般來說,它含有一個列出源程式碼行 (source line) 的堆疊回溯;但它不會顯示從標準輸入中讀取的程式碼。
內建的例外章節列出內建的例外及它們的意義。
8.3. 處理例外¶
編寫程式處理選定的例外是可行的。以下範例會要求使用者輸入內容,直到有效的整數被輸入為止,但它允許使用者中斷程式(使用 Control-C 或作業系統支援的指令);請注意,由使用者產生的程式中斷會引發 KeyboardInterrupt 例外信號。
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
...
try 陳述式運作方式如下。
如果沒有發生例外,則 except 子句會被跳過,
try陳述式執行完畢。如果執行
try子句時發生了例外,則該子句中剩下的部分會被跳過。如果例外的類型與except關鍵字後面的例外名稱相符,則 except 子句被執行,然後,繼續執行 try/except 區塊之後的程式碼。如果發生的例外未符合 except 子句 中的例外名稱,則將其傳遞到外層的
try陳述式;如果仍無法找到處理者,則它是一個未處理例外 (unhandled exception),執行將停止,並顯示錯誤訊息。
try 陳述式可以有不只一個 except 子句,為不同的例外指定處理者,而最多只有一個處理者會被執行。處理者只處理對應的 try 子句中發生的例外,而不會處理同一 try 陳述式裡其他處理者內的例外。一個 except 子句可以列舉多個例外,例如:
... except RuntimeError, TypeError, NameError:
... pass
except 子句中的一個類別符合該類別本身或它的衍生類別的例外(但不是反過來 -- 列出一個衍生類別的 except 子句不會符合它的基底類別的例外)。例如,以下程式碼會按照 B、C、D 的順序印出:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
請注意,如果 except 子句的順序被反轉(把 except B 放到第一個),則會印出 B、B、B ——第一個符合的 except 子句會被觸發。
當例外發生時,它可能有相關聯的值,也就是例外的引數。引數的存在與否及它的類型,是取決於例外的類型。
except 子句可以在例外名稱後面指定一個變數。這個變數被綁定到一個例外實例 (instance),其引數通常儲存在 args 屬性中。為了方便,內建例外型別定義了 __str__() 以印出所有引數而不需顯式地取用 .args:
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # 例外的型別
... print(inst.args) # 儲存在 .args 中的引數
... print(inst) # __str__ 使得引數可以直接被印出,
...