Python Exception 異常處理
在編寫程式時,無論多麼小心,錯誤總是難免會發生。一個穩健 (robust) 的應用程式不僅能完成預期功能,更能在遇到非預期情況時優雅地處理,而不是直接崩潰。這篇文章將帶你深入了解 Python 的異常處理 (Exception Handling) 機制,從基礎概念到進階應用,讓你寫出更可靠的程式碼。
🤔 Error (錯誤) 和 Exception (例外) 有什麼分別?
Python 沒有 Error
類別,只有 Exception
類別。 但他們把不同的 Exception
都稱為 XxxError
,實際上也繼承自 Exception
/ BaseException
,所謂 Error
只是一種稱呼,錯誤其實就是例外。
在 Python 中,所有問題可以簡單歸納為以下兩點:
- 語法錯誤: 語法有問題,就是
SyntaxError
,不能執行 - 例外: 語法沒問題,但執行時出錯,就是一般的
Exception
(稱為XxxError
)
那麼語法錯誤跟例外的區別在哪裡? 發生時間 和 處理方式。
語法錯誤 (SyntaxError)
- 發生時間: 在程式執行 之前 的「解析階段 (parsing phase)」。
- 後果: 由於語法不正確,Python 直譯器無法理解你的程式碼,因此程式根本無法開始執行。
- 處理方式: 你不能使用
try...except
來捕捉SyntaxError
,因為在try
區塊執行之前,程式就已經因語法錯誤而失敗了。你唯一能做的就是修正程式碼。
例如,如果你在 if
語句後面忘記加上冒號 :
:
# 缺少冒號,會導致 SyntaxError
age = 18
if age >= 18
print("You are an adult.")
執行這段程式碼會立即得到錯誤訊息,因為它的語法不正確,程式連執行的機會都沒有。
File "<stdin>", line 2
if age >= 18
^
SyntaxError: invalid syntax
例外 (Exception) 和 執行期錯誤 (Runtime Error)
- 發生時間: 在程式碼執行期間 (at runtime)。
- 後果: 如果未被處理,會立即中斷程式的執行,導致應用程式崩潰 (crash),並在控制台打印出錯誤的追蹤訊息 (traceback)。
- 處理方式: 這些正是
try...except
結構設計用來捕捉和處理的對象,讓你可以優雅地應對問題,避免程式終止。
在 Python 中,所有執行時引發的錯誤都是以「例外 (Exception)」的形式來表示和處理的。換句話說,雖然稱為 Error
,實際上就是 Exception
。
一個常見的例子是除以零:
result = 10 / 0
print("這句話永遠不會被執行")
這段程式碼的語法完全正確,但當它嘗試計算 10 / 0
時,會觸發一個 ZeroDivisionError
例外。這個例外就是一種執行期錯誤,它會導致程式崩潰。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
除了像 ZeroDivisionError
或 ValueError
這樣非常具體的例外,Python 還提供了一個更通用的例外類別:RuntimeError
。 根據官方文件的定義,RuntimeError
是在「偵測到一個不屬於任何其他特定類別的錯誤時」所引發的。它附帶的值是一個字串,用來說明具體出了什麼問題。這使得它成為一個「兜底」的例外,用於處理那些沒有更精確分類的執行期問題。
總結一下關鍵點:
特性 | 語法錯誤 (SyntaxError) | 執行期例外 (e.g., ValueError) |
---|---|---|
本質 | 繼承自 Exception | 繼承自 Exception |
發生時間 | 程式執行前 (解析階段) | 程式執行中 (執行期) |
能否用 try...except 捕捉? | 否 (程式無法啟動) | 是 |
解決方法 | 修正程式碼語法 | 使用 try...except 處理 |
警告 (Warning) 不是錯誤
還有一種情況是「警告 (Warning)」。警告是用來提醒開發者程式碼中可能存在的問題,例如使用了即將被淘汰 (deprecated) 的功能。與例外不同,警告不會中斷程式的執行,它僅僅是個提示訊息。
🥅 捕捉例外:try...except 結構
當你預見某段程式碼可能會引發例外時,你可以使用 try...except
區塊來「捕捉」它,避免程式崩潰。
基本用法
將可能出錯的程式碼放在 try
區塊中,並在 except
區塊中定義處理該例外的方式。
try:
num = int(input("請輸入一個數字: "))
result = 100 / num
print(f"100 除以 {num} 的結果是 {result}")
except ZeroDivisionError:
print("錯誤:你不能輸入 0!")
except ValueError:
print("錯誤:請輸入一個有效的數字!")
print("程式繼續執行...")
在這個例子中:
- 如果用戶輸入
0
,ZeroDivisionError
會被捕捉,並打印相應的錯誤訊息。 - 如果用戶輸入
abc
,ValueError
會被捕捉,因為int()
無法轉換非數字字串。 - 程式在處理完例外後會繼續執行,打印出「程式繼續執行...」。
else
和 finally
子句
try...except
結構還有兩個可選的子句:else
和 finally
。
else
: 如果try
區塊中 沒有發生 任何例外,else
區塊的程式碼將會被執行。finally
: 無論是否發生例外,finally
區塊的程式碼 總會 被執行。這非常適合用來執行清理工作,例如關閉檔案或網絡連接。
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("錯誤:找不到 data.txt 檔案。")
else:
print("檔案內容讀取成功:")
print(content)
finally:
# 確保無論成功或失敗,都會嘗試關閉檔案
# 檢查 file 是否已成功定義,避免在 open() 失敗時引發 NameError
if 'file' in locals() and not file.closed:
file.close()
print("檔案已關閉。")
💡 如何處理例外?
捕捉到例外後,有幾種處理方式。
靜默處理:pass
有時你可能想完全忽略某個例外。你可以在 except
區塊中使用 pass
關鍵字。
import os
try:
# 嘗試刪除一個可能不存在的檔案
os.remove("temp_file.tmp")
except FileNotFoundError:
# 如果檔案不存在,就什麼都不做
pass
⚠️ 注意:過度使用 pass
是一個壞習慣。它會讓錯誤被悄無聲息地隱藏起來,導致除錯變得極為困難。只在你非常確定忽略該例外是安全且正確的行為時才使用它。
專業的處理方式:使用日誌 (Logging)
一個比 pass
更好的做法是記錄錯誤,而不是完全忽略它。這樣既不會打擾用戶,也為開發者留下了追蹤問題的線索。Python 內建的 logging
模組是完成這項任務的絕佳工具。
import logging
# 基本設定:將日誌寫入檔案,記錄等級為 ERROR 或以上
logging.basicConfig(filename='app.log',
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s')
try:
result = 10 / 0
except ZeroDivisionError:
# 記錄詳細的例外資訊到日誌檔案中
logging.exception("發生 ZeroDivisionError")
print("發生了一個內部錯誤,已記錄到日誌中。")
執行後,用戶只會看到一條友好的提示,而 app.log
檔案中會包含完整的錯誤堆疊追蹤 (stack trace),方便開發者分析問題。
// app.log 檔案內容
2025-06-10 14:30:00,123 - ERROR - 發生 ZeroDivisionError
Traceback (most recent call last):
File "your_script.py", line 11, in <module>
result = 10 / 0
ZeroDivisionError: division by zero
✨ 自訂與引發例外
除了處理內建的例外,你還可以定義自己的例外類別,並在適當的時候手動引發它們。
引發例外:raise
使用 raise
關鍵字可以手動觸發一個例外。這在函式接收到無效參數時特別有用。
def set_age(age):
if not isinstance(age, int) or age < 0:
# 引發一個內建的 ValueError
raise ValueError("年齡必須是一個非負整數。")
print(f"年齡已設定為 {age}")
try:
set_age(-5)
except ValueError as e:
print(f"設定失敗:{e}")
建立自訂例外
當內建的例外無法精確描述你的應用程式特有的錯誤時,建立自訂例外是個好主意。自訂例外應繼承自內建的 Exception
類別。
# 1. 定義一個自訂例外類別
class InsufficientBalanceError(Exception):
"""當帳戶餘額不足時引發的例外。"""
pass
# 2. 在程式碼中使用它
def withdraw(balance, amount):
if amount > balance:
# 3. 引發自訂例外
raise InsufficientBalanceError(f"餘額不足,目前餘額 ${balance},嘗試提取 ${amount}")
return balance - amount
# 4. 捕捉自訂例外
my_balance = 500
try:
new_balance = withdraw(my_balance, 1000)
except InsufficientBalanceError as e:
print(f"提款失敗:{e}")
建立自訂例外可以讓你的錯誤處理邏輯更加清晰和具體,提高程式碼的可讀性和可維護性。
📝 結論
異常處理是現代程式設計中不可或缺的一環。掌握 Python 的異常處理機制,能讓你:
- 避免程式崩潰:透過
try...except
捕捉潛在例外。 - 編寫穩健的程式碼:使用
finally
確保資源總能被釋放。 - 提供更好的用戶體驗:向用戶顯示友好的錯誤訊息,而不是混亂的堆疊追蹤。
- 簡化除錯過程:使用
logging
記錄詳細的錯誤資訊。 - 提升程式碼的表達力:透過自訂例外來處理特定的應用程式邏輯問題。
花時間為你的程式碼加上適當的異常處理,是一項非常值得的投資,它將讓你的應用程式變得更加專業和可靠。