Python 異常處理:讓程式堅如磐石 (第 6 章)

站主自己的課程,請大家支持
揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站? 揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站?
  • Post by
  • Dec 20, 2023
post-thumb

在完美的程式世界裡,使用者永遠輸入正確的資料,檔案永遠存在,網路永遠連線暢通。但現實世界並非如此。異常 (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 還提供了 elsefinally 子句,形成完整的處理流程。

無異常
發生異常
try 區塊
else 區塊
except 區塊
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 = default
    
  • EAFP (Easier to Ask for Forgiveness than Permission): 請求原諒比獲得許可容易。先做操作,出錯了再處理。

    # Python 風格
    try:
        value = my_dict['key']
    except KeyError:
        value = default
    

EAFP 的優點

  1. 程式碼更簡潔:專注於「正常路徑」(Happy Path)。
  2. 效能更好:如果檢查 (如 if file_exists) 本身很耗時,且錯誤很少發生,直接嘗試通常更快。
  3. 避免競爭條件 (Race Conditions):在多執行緒環境下,檢查過後狀態可能馬上改變 (如檔案被刪除),EAFP 能確保操作原子性。

6. 總結與最佳實踐

  1. 具體捕捉:避免裸露的 except:,應明確指定如 except FileNotFoundError
  2. 善用 finally:確保資源 (檔案、網路連線、資料庫) 被正確釋放。
  3. 保持 try 區塊精簡:只包覆可能出錯的關鍵程式碼。
  4. 適時拋出:如果你的函數無法處理該錯誤,就讓它向上層傳遞,或包裝成自訂異常後再拋出。
  5. 擁抱 EAFP:寫出更具 Python 味的程式碼。

下一章,我們將進入 Python 函式庫大全 (Libraries),看看如何運用 Python 強大的生態系來處理數學運算、日期時間以及 HTTP 請求!


延伸閱讀

LATEST POST
TAG