客人在數量欄裡打了「三」,收銀臺就在隊伍中間卡住了。這個失敗完全可以預料,恢復也該如此。哪些離群值得接住,哪些應該讓程式乾脆停下來?
錯誤是一種資訊。有些是應該修復的 bug;有些是預期內的失敗,例如使用者輸入了非法數字,或者記錄缺少欄位。try 和 except 可以處理預期內的錯誤,同時不掩蓋其他問題。 你還會在迴圈裡跳過一個壞值,並帶上下文重新拋出例外。
捕獲具體例外
把可能出錯的程式碼放進 try。只捕獲你預期並且能夠處理的例外,例如 int() 失敗產生的 ValueError。不要使用裸 except,因為它會連你需要看到的編程錯誤一起隱藏。
text = "12"
try:
value = int(text)
except ValueError:
value = 0
print(value)安全的型別轉換
不同錯誤,不同處理
字典缺少鍵會觸發 KeyError,文字無法轉成數字會觸發 ValueError。捕獲錯誤的型別不對,處理程式碼不會執行。只捕獲你真正能恢復的例外。
order = {}
try:
price = order["price"]
except KeyError:
price = 0
print(price)缺少欄位
finally 與 else
try 沒有發生例外時,else 會執行。無論成功還是失敗,finally 都會執行,適合做清理工作,例如關閉資源。
try:
print("處理中")
except ValueError:
print("失敗")
else:
print("成功")
finally:
print("結束")完整的 try 結構
只捕獲你能處理的例外
只捕獲你能處理的例外。裸 except 連拼字錯誤和程式錯誤一起吞掉,偵錯會更難。在迴圈裡,為單個壞值捕獲 ValueError,然後繼續處理其餘值。
raw_values = ["3", "many", "5"]
numbers = []
for raw in raw_values:
try:
numbers.append(int(raw))
except ValueError:
print("skipping:", raw)
print(numbers)從一行壞資料中恢復
重新拋出前補充上下文
重新拋出時補充上下文,但不要丟掉原始原因;raise ... from error 會保留例外鏈。用 repr() 顯示壞值,空格和換行才看得見。
def parse_price(raw):
try:
return float(raw)
except ValueError as error:
raise ValueError(f"invalid price: {raw!r}") from error
for raw in ["4.50", "free"]:
try:
print(parse_price(raw))
except ValueError as error:
print(error)保留原因,補上壞輸入
只捕獲你能處理的錯誤
裸 except 會捕獲所有例外,包括你修不了的拼字錯誤和中斷。只捕獲你預期的例外,例如 int() 拋出的 ValueError。else 放成功後才執行的程式碼,finally 放無論如何都要做的清理。如果你說不清拿到這個錯誤之後要做什麼,就讓它繼續向上拋。
raw = "three"
try:
qty = int(raw)
except ValueError:
print("不是數字")
else:
print("數量", qty)
finally:
print("結束")具體處理,明確清理
本課要點
- 只捕獲你能夠處理的具體例外。
- ValueError 對應轉換失敗,KeyError 對應字典缺鍵。
- else 在 try 成功後執行,finally 總是執行。
- 不要用裸 except 靜默吞掉所有 bug。
- 捕獲最窄的例外,重拋時補充上下文。
- 只捕獲你能處理的例外,讓意外錯誤暴露出來。
常見錯誤
- 預期只可能發生 ValueError,卻捕獲了 Exception。
- 把太多程式碼放進 try,導致很難定位錯誤來源。
- 以為在 except 中 print 就夠了;呼叫者可能還需要一個兜底值。
- 用裸 except 把編程錯誤也吞掉。
- 用裸 except 把 try 裡的拼字錯誤一起藏起來。
嘗試把文字轉換成整數,失敗時使用 0 作為兜底,並列印結果。
動手試試
改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。