前言
5. 例外處理 介紹的try…except是被動地捕捉系統產生的錯誤。但有些情況「語法上完全正確、程式也跑得動」,只是不符合我們的業務規則 —— 例如速度變成負數、年齡填了 200 歲。
這種錯誤 Python 不會幫我們發現,必須主動拋出例外來提醒。這就是raise的用途。
raise 的語法
raise [例外類型 [(e)]]- 例外類型:可以是系統內建的例外類型(例如
Exception),也可以是自己定義的例外類型。 - e:是自己提供的參數,可以傳遞例外的資訊。
拋出系統內建的例外
[簡例] 高速公路設定速限為 70~110,如果速度過快或太慢,就主動拋出例外,提醒用路人注意安全。(檔名:raise1.py)
[結果]
現在速度:60,速度太慢了!
目前時速:100
現在速度:150,已經超速了!程式碼:
raise1.pydef CheckSpeed(speed): # 檢查速度 if speed < 70: raise Exception("速度太慢了!") # 拋出 Exception 型別例外 if speed > 110: raise Exception("已經超速了!") # 拋出 Exception 型別例外 for speed in (60, 100, 150): try: CheckSpeed(speed) # 檢查速度 except Exception as e: # 接收 Exception 的例外 print("現在速度:{},{}".format(speed, e)) else: print("目前時速:{}".format(speed))
說明
- 第
1~5行:自訂函式檢查速度是否太慢或超速。- 第
2~3行:如果速度太慢拋出Exception例外,並傳遞「速度太慢了!」訊息。- 第
4~5行:如果速度太快拋出Exception例外,並傳遞「已經超速了!」訊息。- 第
9行:呼叫函式檢查速度。- 第
10~11行:接收Exception的例外,並以參數e顯示接收的訊息。- 第
13行:如果速度正常(沒有拋出例外),else區塊顯示目前時速。
raise會立刻中斷函式一旦執行到
raise,函式立即結束,後面的程式不會執行 —— 效果類似return,但它是「異常結束」,會被try…except接住。這也是為什麼上例中速度 150 時,第一個
if speed < 70不成立、第二個成立就拋出,函式直接結束。
拋出自訂的例外
除了內建的例外類型,我們也可以定義自己的例外類別。書上的寫法是繼承RuntimeError類別:
class MyException(RuntimeError):
def __init__(self, arg):
self.args = arg[簡例] 同上例,如果速度過快或太慢,就主動拋出例外;如果速度正常,就以自訂例外拋出「快樂駕駛,平安返家!」訊息。(檔名:raise2.py)
[結果]
現在速度:60,速度太慢了!
目前時速:100,快樂駕駛,平安返家!
現在速度:150,已經超速了!程式碼:
raise2.pyclass MyException(RuntimeError): def __init__(self, arg): self.args = arg def CheckSpeed(speed): # 檢查速度 if speed < 70: raise Exception("速度太慢了!") if speed > 110: raise Exception("已經超速了!") else: raise MyException("快樂駕駛,平安返家!") # 拋出 MyException 型別例外 def convertTuple(tup): # tuple 轉換為字串 str = ''.join(tup) return str for speed in (60, 100, 150): try: CheckSpeed(speed) except MyException as e: # 接收 MyException 的例外 err = convertTuple(e.args) # tuple 轉換為字串 print("目前時速:{},{}".format(speed, err)) except Exception as e: # 接收 Exception 的例外 print("現在速度:{},{}".format(speed, e))
說明
- 第
1~3行:自訂MyException繼承RuntimeError類別,建構式接收參數arg。- 第
10~11行:速度正常會主動拋出自訂的MyException例外。- 第
13~15行:自訂函式將 tuple 轉換為字串。- 第
20~22行:接收MyException的例外,因為e.args是 tuple 型別,以convertTuple將 tuple 轉換為字串。
except的順序很重要
MyException繼承自RuntimeError,而RuntimeError又屬於Exception。因此**except MyException必須寫在except Exception前面** —— 否則所有例外都會先被Exception接走,自訂例外的區塊永遠不會執行。通則:越specific(子類別)的例外要寫在越前面,
Exception一定放最後。 這與 5. 例外處理 提到的原則一致。
⚠️ 關於書上寫法的兩點補充
一、self.args = arg 會把字串拆成一個個字元
上面的範例為什麼需要convertTuple()把 tuple 接回字串?這其實是self.args = arg造成的副作用。
args是 Python 例外類別的特殊屬性,它規定內容必須是 tuple。當你指派一個字串給它時,Python 會把字串逐字元拆開:
實測結果:
class MyException(RuntimeError):
def __init__(self, arg):
self.args = arg
raise MyException("快樂駕駛,平安返家!")
# e.args → ('快', '樂', '駕', '駛', ',', '平', '安', '返', '家', '!')所以才需要用''.join(e.args)把它「黏」回去。這是自己製造出來、又自己解決的問題。
二、標準寫法不需要覆寫 __init__
Python 的例外類別本來就會處理訊息,直接繼承就好:
class MyException(Exception): # 不用寫 __init__
pass
try:
raise MyException("快樂駕駛,平安返家!")
except MyException as e:
print(e) # 直接印出:快樂駕駛,平安返家!實測結果:
e.args = ('快樂駕駛,平安返家!',) ← 完整字串,而且已經是 tuple
str(e) = '快樂駕駛,平安返家!'| 書上寫法 | 建議寫法 | |
|---|---|---|
需要寫__init__ | ✅ | ❌ 不用 |
需要convertTuple() | ✅ | ❌ 不用 |
| 取得訊息 | ''.join(e.args) | str(e) 或直接 print(e) |
三、繼承 RuntimeError 不是必要的
書上說「定義的類別必須繼承RuntimeError類別」,但實際上繼承Exception即可,這也是 Python 官方建議的做法。
class MyException(Exception): # ✅ 標準做法
passRuntimeError只是Exception眾多子類別中的一個,語意上是指「執行時期的錯誤」。除非你的例外真的屬於這個語意,否則直接繼承Exception更恰當。
什麼時候該自訂例外
當你希望呼叫者能夠分別處理不同的錯誤時就該自訂。
例如一個提款程式,可以定義餘額不足與密碼錯誤兩種例外,讓呼叫端各自給不同的提示訊息 —— 這比回傳False或錯誤代碼清楚得多。
相關主題與延伸閱讀
- 0. 章節索引:本章全部筆記的學習地圖與快速查找。
- 5. 例外處理:
try…except…else…finally的基本用法與例外類別表。 - 12. traceback 與 assert:把例外的完整過程記錄到檔案,以及開發階段的斷言檢查。
- 1. 物件導向程式開發:自訂例外類別會用到類別繼承與建構式的觀念。