Python 入門指南 5.0

單元 8 - 例外處理

~~學習進度表~~

程式中可能會發生的錯誤有三種,分別是語法錯誤 (syntax error) 、執行期間錯誤 (run-time error) 及語意錯誤 (semantic error)

Syntax Errors
→ 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 最後可以再加上 finallyfinally 是無論例外是否發生都會執行的部分,這邊將上例再改寫如下

 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. 例外處理中為什麼要有 elsefinally 的部分?
4. 關鍵字 rasie 的作用是什麼?為什麼要在程式中直接發起例外?
練習
1. 寫一個程式 exercise0801.py ,設定一個變數例如 my_variable 並設定為 10 ,然後在 try 底下印出打錯字的 my_variable ,用一個 exceptNameError 印出 "發生 NameError"參考程式碼
2. 承上題,寫一個程式 exercise0802.py ,在 try 底下改成放 import 然後接一個不存在的模組例如 my_module ,用一個 exceptModuleNotFoundError 印出 "發生 ModuleNotFoundError"參考程式碼
3. 承上題,寫一個程式 exercise0803.py ,在 try 底下改成 while True 迴圈,內容可以放 pass 陳述,用一個 exceptKeyboardInterrupt 印出 "發生 KeyboardInterrupt" ,執行時利用 ctrl + C 結束程式。 參考程式碼
4. 承上題,下一個單元會介紹串列,串列是 Python 中常用可包含多個元素的資料型態,寫一個程式 exercise0804.py ,在 try 底下改成建立串列變數如 a ,並以串列的字面常數設定初值,如 [1, 2, 3] ,然後印出 a[5] ,用一個 exceptIndexError 印出 "發生 IndexError"參考程式碼
5. 承上題,下一個單元同樣會介紹字典,字典也是 Python 中常用可包含多個元素的資料型態,寫一個程式 exercise0805.py ,在 try 底下改成建立串列變數如 a ,並以字典的字面常數設定初值,如 {1:1, 2:2, 3:3} ,然後印出 a[5] ,用一個 exceptKeyError 印出 "發生 KeyError"參考程式碼
6. 寫一個程式 exercise0806.py ,設定一個變數 state 並設定為 True ,在 try 底下放入會發生例外的程式碼,例如 my_variable = 1 / 0 ,在 except 底下重新設定 stateFalse ,並加上 else ,在 else 底下設定 my_variable0 ,在 try-except 之後用 if 判斷 state ,如果為真就印出 my_variable ,如果為假就印出 state參考程式碼
7. 承上題,寫一個程式 exercise0807.py ,將程式碼改為不會出錯,例如 my_variable = 1 / 1參考程式碼
8. 承上題,寫一個程式 exercise0808.py ,將最後依據 state 印出 my_variablestate 的部分放到 finally 底下。 參考程式碼
9. 寫一個程式 exercise0809.py ,在 try 底下放入 raiseraise 後面接例外類別例如 KeyError ,然後在 except 印出 "發生 KeyError"參考程式碼
10. 承上題,寫一個程式 exercise0810.py ,在 try 底下放入數個 raise ,可以用上面練習題用過的例外類別放在 raise 後面,然後用數個 except 處理各自例外發生的情況,觀察 raiseexcept 的順序對執行結果有何不同。 參考程式碼

上一頁 單元 7 - 迴圈
回 Python 入門指南 5.0 首頁
下一頁 單元 9 - 序列、字典與集合
回 Python 教材首頁
回程式語言教材首頁