Learning Objectives
- 分辨語法錯誤(syntax error)與執行時的例外(exception)
- Python 程式例外的流程:例外發生後程式往哪裡走
- 用 try/except 攔截例外,包含多個 except 與 except ... as e
- else、finally 的用法,以及用 raise 引發或重新引發例外
錯誤有兩種
寫程式一定會遇到錯誤。Python 最常見的錯誤分成兩種:
| 種類 | 什麼時候發生 | 例子 |
|---|---|---|
| 語法錯誤(syntax error,又稱剖析錯誤) | 程式還沒開始執行,Python 讀程式碼時就發現寫錯了 | while True print('Hello') 少了冒號 |
| 例外(exception) | 語法正確,但執行到某一行時出了狀況 | 10 * (1/0) 除以 0、int('abc') 無法轉換 |
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^^^^^
SyntaxError: invalid syntax
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
語法錯誤只能修改程式;例外則可以用 try/except 攔截,讓程式在出狀況時有替代方案,而不是直接中止。
Python 內建的例外類型很多,常見的有 ValueError、TypeError、ZeroDivisionError、IndexError、KeyError、NameError、FileNotFoundError。
沒有處理的例外:程式中止
迴圈依序計算 100 / v。播到 v 是 0 的那一圈,看看發生什麼事:紅色提示就是例外,之後的程式(包括最後一行)都不會執行。
try/except 的流程
try:
可能出錯的程式
except 例外類型1:
發生例外類型1 時執行
except 例外類型2:
發生例外類型2 時執行
else:
try 區塊完全沒出錯時執行
finally:
不論有沒有出錯,最後一定執行
- 先執行
try區塊。沒有出錯 → 略過所有except。 - 某一行出錯 →
try裡剩下的程式全部略過,開始由上往下比對except。 - 第一個符合的
except執行(只會執行一個),然後繼續try敘述之後的程式。 - 都不符合 → 例外往外層的
try送;外層都沒有處理,程式就中止並顯示錯誤訊息。
多個 except:第一個符合的才執行
輸入是 10,0。看執行箭頭在 r = n1 / n2 出錯後跳到哪一個 except,以及 print('r =', r) 有沒有執行。下方把輸入改成 10,4、10 4(少了逗號)、ten,2 各跑一次。
else 與 finally
輸入 8,2 沒有出錯,所以會執行 else,最後一定執行 finally。一個 except 可以用元組(tuple)同時攔截多種例外:except (ValueError, ZeroDivisionError):。把輸入改成 8,0 再跑一次,比較兩次的路線。
追蹤 try/except
下面的程式會印出什麼?
try:
print("A", end=" ")
x = int("3")
print("B", end=" ")
except ValueError:
print("C", end=" ")
else:
print("D", end=" ")
finally:
print("E")
下面的程式會印出什麼?
try:
print("A", end=" ")
x = int("three")
print("B", end=" ")
except ValueError:
print("C", end=" ")
else:
print("D", end=" ")
finally:
print("E")
下面的程式會印出什麼?
try:
nums = [1, 2, 3]
print(nums[5])
except Exception:
print("Exception")
except IndexError:
print("IndexError")
except ... as e:取得錯誤資訊
例外可以帶一個物件,提供錯誤的相關資訊。except ValueError as e: 把這個例外物件指派給 e,print(e) 會印出錯誤訊息。看看 'abc' 與 None 分別引發哪一種例外。
except 子句的寫法
| 子句 | 說明 |
|---|---|
except: |
攔截所有例外類型(容易把真正的錯誤藏起來,少用) |
except 名稱: |
只攔截特定例外 |
except 名稱 as e: |
攔截特定例外,並取得例外物件 |
except (名稱1, 名稱2): |
攔截多種例外 |
except (名稱1, 名稱2) as e: |
攔截多種例外,並取得例外物件 |
else: |
沒有例外發生時執行 |
finally: |
不管有沒有發生例外,總是會執行 |
raise:自己引發例外
raise IndexError('Index out of range') 強制引發一個例外,括號裡的文字就是錯誤訊息。第一次輸入 7 超出範圍,被 except 接住後迴圈再問一次;第二次輸入 2 才成功並 break。
重新引發:讓呼叫者也能處理
在 except 裡只寫 raise(不加任何東西),會把同一個例外再丟出去。常見用法是先記錄或印出訊息,再交給呼叫者決定怎麼處理。看例外如何從 to_int 的框架傳回主程式。
想一想:raise 與 finally
下面的程式會印出什麼?
try:
age = -5
if age < 0:
raise ValueError("年齡不能是負數")
print("OK")
except ValueError as e:
print("錯誤:", e)
下面的程式會印出什麼?
def f():
try:
return "try"
finally:
print("finally", end=" ")
print(f())
實作:安全的除法
串列 data 裡每個字串是「被除數,除數」。請用 try/except 完成迴圈:成功時印出商;除以 0 時印出 不能除以 0;不是整數時印出 請輸入整數。
重點精華回顧
- 語法錯誤在執行前就被發現,只能改程式;例外是執行時發生的錯誤,可以攔截處理。
- 例外發生後,
try裡剩下的程式被略過,由上往下比對except,第一個符合的才執行;都不符合就往外層送,最外層也沒處理則程式中止。 except 名稱 as e取得例外物件;except (名稱1, 名稱2)一次攔截多種例外;較精確的例外要放前面。else只在沒有例外時執行;finally不論如何都會執行(連return前也會先執行)。raise 例外類型('訊息')自己引發例外;在except裡單獨寫raise會把同一個例外重新引發給呼叫者。