
在完美的程式世界裡,使用者永遠輸入正確的資料,檔案永遠存在,網路永遠連線暢通。但現實世界並非如此。異常 (Exceptions) 是程式執行過程中發生的錯誤事件,如果不加以處理,它們會導致程式崩潰。
本章將帶您掌握 Python 優雅的錯誤處理機制,讓您的程式即使在惡劣環境下也能穩健運行。
1. 異常處理的核心結構:try-except
Python 使用 try 區塊來包覆可能發生錯誤的程式碼,並用 except 區塊來處理捕捉到的錯誤。
1.1 基本語法
try:
# 可能出錯的程式碼
numerator = int(input("輸入分子: "))
denominator = int(input("輸入分母: "))
result = numerator / denominator
print(f"結果是: {result}")
except ZeroDivisionError:
# 特定錯誤處理
print("錯誤:分母不能為零!")
except ValueError:
# 另一種錯誤處理
print("錯誤:請輸入有效的數字!")
except Exception as e:
# 捕捉剩餘所有錯誤 (不建議濫用)
print(f"發生未預期的錯誤: {e}")
1.2 完整的 try-except-else-finally
除了基礎的捕獲,Python 還提供了 else 和 finally 子句,形成完整的處理流程。
def read_config(filename):
try:
f = open(filename, 'r')
data = f.read()
except FileNotFoundError:
print("設定檔找不到,使用預設值。")
data = "default_settings"
else:
# 只有在 try 成功時執行
print("設定檔讀取成功!")
finally:
# 無論是否出錯,這裡一定會執行 (常用於釋放資源)
if 'f' in locals() and not f.closed:
f.close()
print("檔案已關閉。")
return data
為什麼需要 else?
它能避免 try 區塊中包含太多程式碼。如果某行程式碼 (如處理數據) 本身不預期會拋出該 except 捕獲的異常,就不應該放在 try 裡面,以避免意外捕獲掩蓋了真正的 bug。
2. 異常階層 (Exception Hierarchy)
Python 的異常是類別 (Classes) 物件,且繼承自 BaseException。
| 異常類別 | 描述 |
|---|---|
| BaseException | 所有異常的基類 |
| Exception | 常規錯誤的基類 (自訂異常應繼承此類) |
| ArithmeticError | 數值計算錯誤 (ZeroDivisionError 等) |
| LookupError | 索引或鍵值錯誤 (IndexError, KeyError) |
| ValueError | 值不正確 (如 int("abc")) |
| TypeError | 類型不正確 (如 "3" + 5) |
最佳實踐:盡量捕捉最具體的異常 (Specific Exceptions),而不是籠統地捕捉
Exception。
3. 拋出異常 (Raising Exceptions)
有時候,錯誤並非來自 Python 內部,而是違反了我們的業務邏輯。這時我們可以使用 raise 主動拋出异常。
def deposit(amount):
if amount <= 0:
raise ValueError("存款金額必須大於零")
print(f"成功存入 ${amount}")
try:
deposit(-100)
except ValueError as e:
print(f"操作失敗: {e}")
4. 自訂異常 (User-defined Exceptions)
當內建的異常不足以描述問題時,我們可以定義自己的異常類別。這在大型專案中對於錯誤分類非常有幫助。
class InsufficientFundsError(Exception):
"""當餘額不足時拋出此異常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"餘額不足 (剩餘: {balance}, 需扣款: {amount})")
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
# 使用範例
try:
withdraw(100, 500)
except InsufficientFundsError as e:
print(e) # 輸出詳細的錯誤訊息
5. Pythonic 風格:EAFP vs LBYL
在錯誤處理哲學上,Python 社群推崇 EAFP。
LBYL (Look Before You Leap): 三思而後行。在操作前先做檢查。
# C 語言或 Java 常見風格 if 'key' in my_dict: value = my_dict['key'] else: value = defaultEAFP (Easier to Ask for Forgiveness than Permission): 請求原諒比獲得許可容易。先做操作,出錯了再處理。
# Python 風格 try: value = my_dict['key'] except KeyError: value = default
EAFP 的優點:
- 程式碼更簡潔:專注於「正常路徑」(Happy Path)。
- 效能更好:如果檢查 (如
if file_exists) 本身很耗時,且錯誤很少發生,直接嘗試通常更快。 - 避免競爭條件 (Race Conditions):在多執行緒環境下,檢查過後狀態可能馬上改變 (如檔案被刪除),EAFP 能確保操作原子性。
6. 總結與最佳實踐
- 具體捕捉:避免裸露的
except:,應明確指定如except FileNotFoundError。 - 善用
finally:確保資源 (檔案、網路連線、資料庫) 被正確釋放。 - 保持
try區塊精簡:只包覆可能出錯的關鍵程式碼。 - 適時拋出:如果你的函數無法處理該錯誤,就讓它向上層傳遞,或包裝成自訂異常後再拋出。
- 擁抱 EAFP:寫出更具 Python 味的程式碼。
下一章,我們將進入 Python 函式庫大全 (Libraries),看看如何運用 Python 強大的生態系來處理數學運算、日期時間以及 HTTP 請求!
延伸閱讀: