Skip to content

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 語句後面忘記加上冒號 :

python
# 缺少冒號,會導致 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

一個常見的例子是除以零:

python
result = 10 / 0
print("這句話永遠不會被執行")

這段程式碼的語法完全正確,但當它嘗試計算 10 / 0 時,會觸發一個 ZeroDivisionError 例外。這個例外就是一種執行期錯誤,它會導致程式崩潰。

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

除了像 ZeroDivisionErrorValueError 這樣非常具體的例外,Python 還提供了一個更通用的例外類別:RuntimeError。 根據官方文件的定義,RuntimeError 是在「偵測到一個不屬於任何其他特定類別的錯誤時」所引發的。它附帶的值是一個字串,用來說明具體出了什麼問題。這使得它成為一個「兜底」的例外,用於處理那些沒有更精確分類的執行期問題。

總結一下關鍵點:

特性語法錯誤 (SyntaxError)執行期例外 (e.g., ValueError)
本質繼承自 Exception繼承自 Exception
發生時間程式執行前 (解析階段)程式執行中 (執行期)
能否用 try...except 捕捉? (程式無法啟動)
解決方法修正程式碼語法使用 try...except 處理

警告 (Warning) 不是錯誤

還有一種情況是「警告 (Warning)」。警告是用來提醒開發者程式碼中可能存在的問題,例如使用了即將被淘汰 (deprecated) 的功能。與例外不同,警告不會中斷程式的執行,它僅僅是個提示訊息。

🥅 捕捉例外:try...except 結構

當你預見某段程式碼可能會引發例外時,你可以使用 try...except 區塊來「捕捉」它,避免程式崩潰。

基本用法

將可能出錯的程式碼放在 try 區塊中,並在 except 區塊中定義處理該例外的方式。

python
try:
    num = int(input("請輸入一個數字: "))
    result = 100 / num
    print(f"100 除以 {num} 的結果是 {result}")
except ZeroDivisionError:
    print("錯誤:你不能輸入 0!")
except ValueError:
    print("錯誤:請輸入一個有效的數字!")

print("程式繼續執行...")

在這個例子中:

  • 如果用戶輸入 0ZeroDivisionError 會被捕捉,並打印相應的錯誤訊息。
  • 如果用戶輸入 abcValueError 會被捕捉,因為 int() 無法轉換非數字字串。
  • 程式在處理完例外後會繼續執行,打印出「程式繼續執行...」。

elsefinally 子句

try...except 結構還有兩個可選的子句:elsefinally

  • else: 如果 try 區塊中 沒有發生 任何例外,else 區塊的程式碼將會被執行。
  • finally: 無論是否發生例外finally 區塊的程式碼 總會 被執行。這非常適合用來執行清理工作,例如關閉檔案或網絡連接。
python
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 關鍵字。

python
import os
try:
    # 嘗試刪除一個可能不存在的檔案
    os.remove("temp_file.tmp")
except FileNotFoundError:
    # 如果檔案不存在,就什麼都不做
    pass

⚠️ 注意:過度使用 pass 是一個壞習慣。它會讓錯誤被悄無聲息地隱藏起來,導致除錯變得極為困難。只在你非常確定忽略該例外是安全且正確的行為時才使用它。

專業的處理方式:使用日誌 (Logging)

一個比 pass 更好的做法是記錄錯誤,而不是完全忽略它。這樣既不會打擾用戶,也為開發者留下了追蹤問題的線索。Python 內建的 logging 模組是完成這項任務的絕佳工具。

python
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),方便開發者分析問題。

log
// 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 關鍵字可以手動觸發一個例外。這在函式接收到無效參數時特別有用。

python
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 類別。

python
# 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 記錄詳細的錯誤資訊。
  • 提升程式碼的表達力:透過自訂例外來處理特定的應用程式邏輯問題。

花時間為你的程式碼加上適當的異常處理,是一項非常值得的投資,它將讓你的應用程式變得更加專業和可靠。

📚 參考資料

KF Software House