Python 入門指南 5.0
單元 8 - 例外處理
程式中可能會發生的錯誤有三種,分別是語法錯誤 (syntax error) 、執行期間錯誤 (run-time error) 及語意錯誤 (semantic error)
→ compiler or interpreter
Runtime Errors
→ exception handling
Semantic Errors
→ programmer
直譯器 (interpreter) 會直接幫我們挑出語法錯誤,例如單元 2 漏打 print() 函數 (function) 右邊的小括弧,直譯器在執行的時候就會在錯誤 (error) 的地方停下來,顯示相關的錯誤資訊。
至於執行期間錯誤是可預期的錯誤,為什麼說是可預期的呢?例如像是除以 0 會造成致命錯誤 (fatal error) ,因此我們寫程式 (program) 就要想辦法不讓除數變成 0 ,但是除數如果需要計算,會不會變成 0 就很難講,這時候就需要靠例外處理 (exception handling) 機制來對例外 (exception) 進行處理。
語意錯誤是三種錯誤中最麻煩的錯誤,因為有語意錯誤的程式,程式可以順利執行完畢,卻跑出錯誤的結果,我們會在單元 25 詳細介紹如何處理語意錯誤。
基本的例外處理是把有可能發生例外的程式碼放在關鍵字 (keyword) try 之下縮排 (indentation) 的程式碼,然後用 except 接例外的類別識別字, except 底下縮排的地方放處理例外的程式碼,舉例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # try 陳述
try:
# 預計發生錯誤的地方
demo = 1 / 0
# 印出變數 demo
print(demo)
# 檢測是否發生 ZeroDivisionError
except ZeroDivisionError:
print("除數不能為 0")
# 檢測除了以上其他一切錯誤
except:
print("發生未偵測到的錯誤")
# try 陳述結束後執行
print("try 陳述後執行的部分")
# 檔名: try_demo01.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
先看到 try 的部分,注意 try 最後要加上冒號
2 3 4 5 6 | try:
# 預計發生錯誤的地方
demo = 1 / 0
# 印出變數 demo
print(demo)
|
第 4 行會發生除以 0 的致命錯誤
4 | demo = 1 / 0
|
如果沒有發生錯誤,第 6 行會印出變數 (variable) demo
6 | print(demo)
|
底下有兩組 except ,先看到第一組 except ,注意 except 後面空一格接例外類別 (class) 的識別字 (identifier) ,最後要加上冒號
8 9 | except ZeroDivisionError:
print("除數不能為 0")
|
除數為 0 的內建例外名稱為 ZeroDivisionError ,如果執行 try 底下縮排的程式碼發生 ZeroDivisionError ,就會執行此處 except 底下縮排的程式碼,也就是印出提示訊息除數不能為 0 。
第二組後面沒有接例外類別的識別字,直接加上冒號
11 12 | except:
print("發生未偵測到的錯誤")
|
這會檢測所有發生的例外,然後印出相對應的訊息。
此例執行結果如下
$ python try_demo01.py |
除數不能為 0 |
try 陳述後執行的部分 |
$ |
簡單說,打可能會發生例外的程式碼放到 try 底下,然後用 except 處理個別例外,就能讓程式順利執行,而不會被例外而中斷。
例外處理可以加上 else 陳述 (statement) , else 為沒有發生例外而後執行的部分,這邊將上例改寫如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # try 陳述
try:
# 預計發生錯誤的地方
demo = 1 / 1
# 印出變數 demo
print(demo)
# 檢測是否發生 ZeroDivisionError
except ZeroDivisionError:
print("除數不能為 0")
# 檢測除了以上其他一切錯誤
except:
print("發生未偵測到的錯誤")
# 沒有發生錯誤就會執行的部分
else:
print("沒有發生錯誤")
# try 陳述結束後執行
print("try 陳述後執行的部分")
# 檔名: try_demo02.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 4 月
|
執行結果如下
$ python try_demo02.py |
1.0 |
沒有發生錯誤 |
try 陳述後執行的部分 |
$ |
由於 1 除以 1 不會發生錯誤
4 | demo = 1 / 1
|
因此最後會執行 else 的部分
14 15 | else:
print("沒有發生錯誤")
|
try-except-else 最後可以再加上 finally , finally 是無論例外是否發生都會執行的部分,這邊將上例再改寫如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # 設定變數 demo
demo = 0
# try 陳述
try:
# 預計發生錯誤的地方
demo = 1 / 0
# 以下不會印出變數 demo
print(demo)
# 檢測是否發生 ZeroDivisionError
except ZeroDivisionError:
print("除數不能為 0")
# 重新設定變數 demo
demo = 1
# 檢測除了以上其他一切錯誤
except:
print("發生未偵測到的錯誤")
# 沒有發生錯誤就會執行的部分
else:
print("沒有發生錯誤")
# 無論是否發生錯誤都會執行的部分
finally:
# 最後會印出變數 demo
print(demo)
# try 陳述結束後執行
print("try 陳述後執行的部分")
# 檔名: try_demo03.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
發生例外會將變數 demo 重新設定
11 12 13 14 | except ZeroDivisionError:
print("除數不能為 0")
# 重新設定變數 demo
demo = 1
|
最後 finally 無論是否發生例外都會印出變數 demo 值
22 23 24 | finally:
# 最後會印出變數 demo
print(demo)
|
執行結果如下
$ python try_demo03.py |
除數不能為 0 |
1 |
try 陳述後執行的部分 |
$ |
此外關鍵字 raise 可以直接發起例外,舉例如下
1 2 3 4 5 6 7 8 9 10 11 | # raise 陳述
raise Exception("示範 raise 陳述")
# raise 陳述結束後執行
print("raise 陳述後執行的部分")
# 檔名: raise_demo.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
由於 raise 會直接發起例外,因此執行結果會直接印出例外的錯誤訊息
$ python raise_demo.py |
Traceback (most recent call last): |
File "raise_demo.py", line 2, in <module> |
raise Exception("示範 raise 陳述") |
Exception: 示範 raise 陳述 |
$ |
所以最後並不會印出 raise 陳述後執行的部分。
Python 的基礎複合陳述到這裡介紹完畢,接下來進入模組化程式設計之前,我們先來看看 Python 內建的複合型態 (compound data type) ,包括序列 (sequence) 、字典 (dictionary) 以及集合 (set) 。
中英文術語對照 | |
---|---|
類別 | class |
複合型態 | compound data type |
字典 | dictionary |
錯誤 | error |
例外 | exception |
例外處理 | exception handling |
致命錯誤 | fatal error |
函數 | function |
識別字 | identifier |
直譯器 | interpreter |
關鍵字 | keyword |
程式 | program |
執行期間錯誤 | run-time error |
語意錯誤 | semantic error |
序列 | sequence |
集合 | set |
陳述 | statement |
語法錯誤 | syntax error |
變數 | variable |
重點整理 |
---|
1. 程式中可能會發生的錯誤有三種,分別是語法錯誤、執行期間錯誤及語意錯誤。 |
2. 直譯器會在程式執行時直接挑出語法錯誤。 |
3. 執行期間錯誤為程式中預期可能發生的錯誤,這叫做例外,包括內建的例外類別及自行定義的例外類別。 |
4. 例外處理是指程式執行期間發生例外的時候,利用 try-except 陳述保持程式進行執行。 |
5. 關鍵字 else 可以接在 try-except 陳述後面,作為沒有發生例外執行的部分。 |
6. 關鍵字 finally 可以放在 try-except 陳述最後,作為無論是否發生例外都會執行的部分。 |
7. 關鍵字 rasie 可以直接發生例外,因此如果程式碼含 raise 陳述就要進行例外處理。 |
問題與討論 |
---|
1. 為什麼語意錯誤是最難處理的錯誤? |
2. 為什麼要做例外處理?如果程式發生例外不做例外處理會怎麼樣? |
3. 例外處理中為什麼要有 else 及 finally 的部分? |
4. 關鍵字 rasie 的作用是什麼?為什麼要在程式中直接發起例外? |
練習 |
---|
1. 寫一個程式 exercise0801.py ,設定一個變數例如 my_variable 並設定為 10 ,然後在 try 底下印出打錯字的 my_variable ,用一個 except 接 NameError 印出 "發生 NameError" 。 參考程式碼 |
2. 承上題,寫一個程式 exercise0802.py ,在 try 底下改成放 import 然後接一個不存在的模組例如 my_module ,用一個 except 接 ModuleNotFoundError 印出 "發生 ModuleNotFoundError" 。 參考程式碼 |
3. 承上題,寫一個程式 exercise0803.py ,在 try 底下改成 while True 迴圈,內容可以放 pass 陳述,用一個 except 接 KeyboardInterrupt 印出 "發生 KeyboardInterrupt" ,執行時利用 ctrl + C 結束程式。 參考程式碼 |
4. 承上題,下一個單元會介紹串列,串列是 Python 中常用可包含多個元素的資料型態,寫一個程式 exercise0804.py ,在 try 底下改成建立串列變數如 a ,並以串列的字面常數設定初值,如 [1, 2, 3] ,然後印出 a[5] ,用一個 except 接 IndexError 印出 "發生 IndexError" 。 參考程式碼 |
5. 承上題,下一個單元同樣會介紹字典,字典也是 Python 中常用可包含多個元素的資料型態,寫一個程式 exercise0805.py ,在 try 底下改成建立串列變數如 a ,並以字典的字面常數設定初值,如 {1:1, 2:2, 3:3} ,然後印出 a[5] ,用一個 except 接 KeyError 印出 "發生 KeyError" 。 參考程式碼 |
6. 寫一個程式 exercise0806.py ,設定一個變數 state 並設定為 True ,在 try 底下放入會發生例外的程式碼,例如 my_variable = 1 / 0 ,在 except 底下重新設定 state 為 False ,並加上 else ,在 else 底下設定 my_variable 為 0 ,在 try-except 之後用 if 判斷 state ,如果為真就印出 my_variable ,如果為假就印出 state 。 參考程式碼 |
7. 承上題,寫一個程式 exercise0807.py ,將程式碼改為不會出錯,例如 my_variable = 1 / 1 。 參考程式碼 |
8. 承上題,寫一個程式 exercise0808.py ,將最後依據 state 印出 my_variable 或 state 的部分放到 finally 底下。 參考程式碼 |
9. 寫一個程式 exercise0809.py ,在 try 底下放入 raise , raise 後面接例外類別例如 KeyError ,然後在 except 印出 "發生 KeyError" 。 參考程式碼 |
10. 承上題,寫一個程式 exercise0810.py ,在 try 底下放入數個 raise ,可以用上面練習題用過的例外類別放在 raise 後面,然後用數個 except 處理各自例外發生的情況,觀察 raise 及 except 的順序對執行結果有何不同。 參考程式碼 |