前言

所謂「例外」(Exception),就是當程式碼在編譯期間沒有出現錯誤訊息,而在程式執行時發生錯誤,這種錯誤稱為執行時期錯誤。進行例外處理是不希望程式中斷。而是希望程式能捕捉錯誤,進行錯誤補救,並繼續執行程式。

若錯誤是使用者輸入不正確資料所造成的,可以要求使用者再輸入正確資料後才繼續執行。

Python使用 try … except … finally 敘述來解決例外處理,它的方式是將被監視的敘述區段寫在try: 的程式區塊,當程式執行到 try: 內的敘述有發生錯誤時,會逐一檢查該錯誤所屬的例外類別,以便執行該except:內的敘述。最後不管是否有符合except,都會執行最後的finally: 敘述區段。

例外處理的語法如下:

try:
    受監視的敘述區段
 
except 例外類別1[as e]:
    處理錯誤的敘述區段1
 
except 例外類別2[as e]:
    處理錯誤的敘述區段2
 
except Exception [as e] :
    處理其它錯誤的敘述區段
 
finally:
    最後會執行敘述區段
  1. 使用try: 敘述時,至少要有一個檢查except(捕捉)finally:敘述區段配合。

  2. 多個檢查except(捕捉)敘述區段,由上至下except逐一檢查,若遇到符合條件的例外類別,則執行該對應敘述區段,則以下的except就不再處理。

  3. 常用到的例外類別如下表:

    例外類別說明
    ZeroDivisionError除數為 0 的算術運算錯誤。
    ValueError數值錯誤。如使用內建函式時,參數型別與傳入值不符。
    NameError變數名稱未定義,而直接運算產生的錯誤。
    IndexError串引註標(索引)超出宣告範圍
    IOErrorI/O 異常處理所產生錯誤。
    FileNotFoundError檔案或資料夾找不到時所產生的錯誤。
    Exception程式執行時,所有內建、非系統引發所產生的異常錯誤。
  4. finally : 敘述區段在最後一個except之後,不論是否有執行 except 敘述區段,都會執行finally:敘述區段。finally:敘述區段也可以省略。

  5. 透過[e]取得錯誤資訊,可以用print(e)來顯示錯誤訊息。

[簡例] 兩數相除,程式沒有應用例外處理技巧,觀察除數為0時電腦如何處理。 (檔名:try01.py)

[結果]

ZeroDivisionError: division by zero

說明

  1. 1~3行:定義div(n1,n2)函式,用來計算兩個整數相除,然後顯示其結果。
  2. 5行:呼叫div(8,0)函式,因除數n2=0,所以第2行不能被執行,程式被迫中斷,並出現「ZeroDivisionError:division by zero」錯誤訊息。
  3. 6行:呼叫div(8,5)函式,因程式被迫中斷,此行沒被執行。

[簡例] 兩數相除,程式有應用例外處理技巧,觀察除數為0時電腦如何處理。 (檔名:try02.py)
[結果]

錯誤類型: division by zero
執行 finally: 敘述
 
8 / 5 = 1.6
執行 finally: 敘述

說明

  1. 1-9行: 定義div(n1,n2)函式,用來計算兩個整數相除,然後顯示其結果。在此方法中應用例外處理try····except⋯finally⋯技巧。
  2. 11行: 呼叫div(8,0)函式,因除數n2=0,所以第34行不會被執行,而直接跳到第 5行執行except捕捉例外。
  3. 5行: 捕捉到例外,其錯誤類型是「division by zero」,ZeroDivisionError例外類別,由第67行顯示出來。
  4. 89行:無論有否執行except:敘述區段,皆會執行finally:敘述區段。
  5. 12行: 本程式不會中斷執行,呼叫div(8,5)函式,因此會執行「8/5 = 1.6」 的結果。

[簡例] 當串列註標(索引)超出範圍時,利用try⋯except⋯來處理。本例使用多例使用多個except來針對特定例外類別進行偵測捕捉。 (檔名:try03.py)

[結果]

arr[4] = 40
錯誤類型 : 串列註標超出範圍

說明

  1. 1行:定義串列 arr,其註標範圍為0~4,共5個元素。
  2. 3,4行: 正常執行。
  3. 5行:arr[9],其註標(索引值)為9,已超出範圍,因此第5,6行,開始進行例外捕捉。本例共設計了三層偵測捕捉的關卡。
    • 首先跳至第7行接受偵測,第7行是偵測「算術運算除數為零錯誤」。
    • 若非第7行的錯誤類型,則再跳至第9行接受偵測,第9行是偵錯 「串列註標超出範圍」。
    • 若非第9行的錯誤類型,則再跳至第11行接受偵測,第11行是偵錯程式執行時產生的其它錯誤。
      很明顯第5行的錯誤是「串列註標超出範圍」,會在第9行被偵測到,故執行了第9~10行的敘述,然後離開try···except⋯敘述區段,因此不會跳至第11行接受偵測。
  4. 11~12行: 例外類別Exception要列為最後一項,若移到except敘述區段最前面時,則其餘例外類別要刪除,因後面的except 敘述區段都不會被執行是多餘的。

else 子句

除了tryexceptfinally,例外處理還可以加上else子句。else只有在try區塊「完全沒有發生例外」時才會執行

完整語法的四個區塊:

try:
    可能引發例外的程式區塊
except 例外類別 as e:
    發生例外時執行
else:
    沒有發生例外時才執行      # ← 本節新增
finally:
    不論如何都會執行
區塊執行時機
try一定會開始執行,發生例外就中斷跳到except
except發生對應例外時
else沒有發生任何例外時
finally不論有無例外,一定執行

[簡例] 輸入兩個整數求餘數,同時處理「非數值輸入」與「除數為 0」兩種錯誤。(檔名:trymod.py

[結果]

請輸入第一個整數:5
請輸入第二個整數:3
r= 2
一定會執行的程式區塊

說明

  1. 1~4行:輸入 ab 兩數後求餘數。
  2. 5~6行:先捕捉輸入非數值字元引起的 ValueError
  3. 7~8行:以except Exception as e捕捉其他所有例外(包含分母為 0),並利用參數e顯示錯誤訊息。
  4. 9~10行:若try內指令運算正確,才顯示餘數。
  5. 11~12行:不論是否發生錯誤,都會執行finally區塊。

為什麼要用 else 而不是直接寫在 try 裡?

把「成功才要做的事」放在else,可以讓try區塊只包含真正可能出錯的那幾行
若把print("r=", r)也寫進try,萬一這行自己出錯,會被誤判成是計算出錯,反而讓除錯變困難。
原則:try 區塊越小越好。

用一個 except 捕捉多個例外

如果多種例外的處理方式相同,可以把它們寫在同一個except的小括號中,不必分開寫。

語法:

try:
    可能引發例外的程式區塊
except (例外一, 例外二, …) [as e]:
    處理例外的程式區塊

[簡例] 同時捕捉「非數值輸入」與「除數為 0」兩種錯誤。(檔名:trymod2.py

括號不可以省略

必須寫成except (ValueError, ZeroDivisionError):小括號不能省略
而且要注意這是「這幾種例外都用同一段程式處理」,若各種錯誤要給不同訊息,還是得分開寫多個except

顯示內建的錯誤訊息

在多個例外後面一樣可以加上as e,用參數e取得系統內建的錯誤說明。

執行時會顯示 Python 內建的英文錯誤訊息,例如:

發生 integer division or modulo by zero 的錯誤!
發生 invalid literal for int() with base 10: 'a' 的錯誤!

內建訊息是英文的

e取得的是 Python 內建的英文說明,適合開發階段除錯使用。
若是要給一般使用者看的畫面,建議自己寫中文訊息,或兩者併用(中文提示 + 英文細節)。


相關主題與延伸閱讀