前言
所謂「例外」(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:
最後會執行敘述區段-
使用
try: 敘述時,至少要有一個檢查except(捕捉)或finally:敘述區段配合。 -
多個檢查
except(捕捉)敘述區段,由上至下except逐一檢查,若遇到符合條件的例外類別,則執行該對應敘述區段,則以下的except就不再處理。 -
常用到的例外類別如下表:
例外類別 說明 ZeroDivisionError 除數為 0 的算術運算錯誤。 ValueError 數值錯誤。如使用內建函式時,參數型別與傳入值不符。 NameError 變數名稱未定義,而直接運算產生的錯誤。 IndexError 串引註標(索引)超出宣告範圍 IOError I/O 異常處理所產生錯誤。 FileNotFoundError 檔案或資料夾找不到時所產生的錯誤。 Exception 程式執行時,所有內建、非系統引發所產生的異常錯誤。 -
finally: 敘述區段在最後一個except之後,不論是否有執行except敘述區段,都會執行finally:敘述區段。finally:敘述區段也可以省略。 -
透過
[e]取得錯誤資訊,可以用print(e)來顯示錯誤訊息。
[簡例] 兩數相除,程式沒有應用例外處理技巧,觀察除數為0時電腦如何處理。 (檔名:try01.py)
[結果]
ZeroDivisionError: division by zero程式碼: try01.py
def div(n1, n2): res = n1 / n2 print(f'{n1} / {n2} = {res}') div(8, 0) div(8, 5)
說明
- 第
1~3行:定義div(n1,n2)函式,用來計算兩個整數相除,然後顯示其結果。- 第
5行:呼叫div(8,0)函式,因除數n2=0,所以第2行不能被執行,程式被迫中斷,並出現「ZeroDivisionError:division by zero」錯誤訊息。- 第
6行:呼叫div(8,5)函式,因程式被迫中斷,此行沒被執行。
[簡例] 兩數相除,程式有應用例外處理技巧,觀察除數為0時電腦如何處理。 (檔名:try02.py)
[結果]
錯誤類型: division by zero
執行 finally: 敘述
8 / 5 = 1.6
執行 finally: 敘述程式碼:
try02.pydef div(n1, n2): try: res = n1 / n2 print(f'{n1} / {n2} = {res}') except Exception as e: print('錯誤類型 :', end =' ') print(e) finally: print('執行 finally: 敘述\n') div(8, 0) div(8, 5)
說明
- 第
1-9行: 定義div(n1,n2)函式,用來計算兩個整數相除,然後顯示其結果。在此方法中應用例外處理try····except⋯finally⋯技巧。- 第
11行: 呼叫div(8,0)函式,因除數n2=0,所以第3、4行不會被執行,而直接跳到第5行執行except捕捉例外。- 第
5行: 捕捉到例外,其錯誤類型是「division by zero」,ZeroDivisionError例外類別,由第6、7行顯示出來。- 第
8、9行:無論有否執行except:敘述區段,皆會執行finally:敘述區段。- 第
12行: 本程式不會中斷執行,呼叫div(8,5)函式,因此會執行「8/5 = 1.6」的結果。
[簡例] 當串列註標(索引)超出範圍時,利用try⋯except⋯來處理。本例使用多例使用多個except來針對特定例外類別進行偵測捕捉。 (檔名:try03.py)
[結果]
arr[4] = 40
錯誤類型 : 串列註標超出範圍程式碼: try03.py
arr = [0 for x in range(5)] try: arr[4] = 40 print(f'arr[4] = {arr[4]}') arr[9] = 90 print(f'arr[9] = {arr[9]}') except ZeroDivisionError: print('錯誤類型 : 除數為零') except IndexError: print('錯誤類型 : 串列註標超出範圍') except Exception as e: print('錯誤類型 :', e)
說明
- 第
1行:定義串列arr,其註標範圍為0~4,共5個元素。- 第
3,4行: 正常執行。- 第
5行:arr[9],其註標(索引值)為9,已超出範圍,因此第5,6行,開始進行例外捕捉。本例共設計了三層偵測捕捉的關卡。
- 首先跳至第
7行接受偵測,第7行是偵測「算術運算除數為零錯誤」。- 若非第
7行的錯誤類型,則再跳至第9行接受偵測,第9行是偵錯 「串列註標超出範圍」。- 若非第
9行的錯誤類型,則再跳至第11行接受偵測,第11行是偵錯程式執行時產生的其它錯誤。
很明顯第5行的錯誤是「串列註標超出範圍」,會在第9行被偵測到,故執行了第9~10行的敘述,然後離開try···except⋯敘述區段,因此不會跳至第11行接受偵測。- 第
11~12行: 例外類別Exception要列為最後一項,若移到except敘述區段最前面時,則其餘例外類別要刪除,因後面的except敘述區段都不會被執行是多餘的。
else 子句
除了try、except、finally,例外處理還可以加上else子句。else只有在try區塊「完全沒有發生例外」時才會執行。
完整語法的四個區塊:
try:
可能引發例外的程式區塊
except 例外類別 as e:
發生例外時執行
else:
沒有發生例外時才執行 # ← 本節新增
finally:
不論如何都會執行| 區塊 | 執行時機 |
|---|---|
try | 一定會開始執行,發生例外就中斷跳到except |
except | 有發生對應例外時 |
else | 沒有發生任何例外時 |
finally | 不論有無例外,一定執行 |
[簡例] 輸入兩個整數求餘數,同時處理「非數值輸入」與「除數為 0」兩種錯誤。(檔名:trymod.py)
[結果]
請輸入第一個整數:5
請輸入第二個整數:3
r= 2
一定會執行的程式區塊程式碼:
trymod.pytry: a = int(input("請輸入第一個整數:")) b = int(input("請輸入第二個整數:")) r = a % b except ValueError: print("發生輸入非數值的錯誤!") except Exception as e: print("發生", e, "的錯誤,包括分母為 0 的錯誤!") else: print("r=", r) finally: print("一定會執行的程式區塊")
說明
- 第
1~4行:輸入a、b兩數後求餘數。- 第
5~6行:先捕捉輸入非數值字元引起的ValueError。- 第
7~8行:以except Exception as e捕捉其他所有例外(包含分母為 0),並利用參數e顯示錯誤訊息。- 第
9~10行:若try內指令運算正確,才顯示餘數。- 第
11~12行:不論是否發生錯誤,都會執行finally區塊。
為什麼要用
else而不是直接寫在try裡?把「成功才要做的事」放在
else,可以讓try區塊只包含真正可能出錯的那幾行。
若把print("r=", r)也寫進try,萬一這行自己出錯,會被誤判成是計算出錯,反而讓除錯變困難。
原則:try區塊越小越好。
用一個 except 捕捉多個例外
如果多種例外的處理方式相同,可以把它們寫在同一個except的小括號中,不必分開寫。
語法:
try:
可能引發例外的程式區塊
except (例外一, 例外二, …) [as e]:
處理例外的程式區塊[簡例] 同時捕捉「非數值輸入」與「除數為 0」兩種錯誤。(檔名:trymod2.py)
程式碼:
trymod2.pytry: a = int(input("請輸入第一個整數:")) b = int(input("請輸入第二個整數:")) r = a % b except (ValueError, ZeroDivisionError): print("發生輸入非數值的錯誤或分母為 0 的錯誤!") else: print("r=", r)
括號不可以省略
必須寫成
except (ValueError, ZeroDivisionError):,小括號不能省略。
而且要注意這是「這幾種例外都用同一段程式處理」,若各種錯誤要給不同訊息,還是得分開寫多個except。
顯示內建的錯誤訊息
在多個例外後面一樣可以加上as e,用參數e取得系統內建的錯誤說明。
程式碼:
trymod3.pytry: a = int(input("請輸入第一個整數:")) b = int(input("請輸入第二個整數:")) r = a % b except (ValueError, ZeroDivisionError) as e: print("發生 {} 的錯誤!".format(e)) else: print("r=", r)
執行時會顯示 Python 內建的英文錯誤訊息,例如:
發生 integer division or modulo by zero 的錯誤!
發生 invalid literal for int() with base 10: 'a' 的錯誤!內建訊息是英文的
e取得的是 Python 內建的英文說明,適合開發階段除錯使用。
若是要給一般使用者看的畫面,建議自己寫中文訊息,或兩者併用(中文提示 + 英文細節)。
相關主題與延伸閱讀
- 0. 章節索引:本章全部筆記的學習地圖與快速查找。
- 6. os.path 路徑處理:路徑操作失敗時常需搭配例外處理。
- 9. 檔案編碼與 BOM 處理:編碼不符會引發
UnicodeDecodeError,是常見的例外情境。 - 11. raise 主動拋出例外:除了被動捕捉,也可以在指定條件下主動拋出例外。
- 12. traceback 與 assert:把例外過程記錄到檔案,以及開發階段的斷言檢查。
- 1. 檔案概論:檔案處理過程中常需搭配例外處理來因應各種錯誤情況。
- 3. 檔案的開啟與關閉:開啟不存在的檔案會產生
FileNotFoundError,是例外處理常見的應用情境。 - 4. 文字檔資料的寫入與讀取:讀寫檔案時搭配例外處理,能避免程式因錯誤而中斷。
- 1. 物件導向程式開發:物件導向中解構式
__del__常結合例外處理,確保資源被正確釋放。 - 2. 資料夾的建立與刪除:建立或刪除資料夾失敗時,同樣可運用例外處理進行防呆。