Python Context Managers:with 語句與資源管理 (第 19 章)

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

在寫程式時,我們經常需要管理「資源」,例如:開啟檔案、連線資料庫、取得執行緒鎖 (Lock)。這些資源使用完畢後與必須被釋放,否則會導致記憶體洩漏或系統崩潰。

這就是 Context Manager (上下文管理器)with 語句 登場的時刻。

1. 為什麼需要 with

傳統寫法 (不安全)

f = open("data.txt", "w")
f.write("Hello")
# 如果這裡發生錯誤 (Exception),下一行不會執行!
# 檔案將永遠保持開啟狀態,佔用資源。
f.close() 

Try-Finally 寫法 (安全但囉唆)

f = open("data.txt", "w")
try:
    f.write("Hello")
finally:
    # 保證無論有無錯誤都會執行
    f.close()

With 寫法 (安全又優雅)

with open("data.txt", "w") as f:
    f.write("Hello")
# 離開縮排區塊後,f.close() 會自動被呼叫

with 語句保證了資源的正確釋放,是 Python 中最優美的語法之一。

2. 實作 Context Manager

要讓一個物件支援 with 語句,必須實作 Context Manager Protocol:

  1. __enter__(self): 進入 with 區塊時執行。回傳值會賦予給 as 後面的變數。
  2. __exit__(self, exc_type, exc_value, traceback): 離開區塊時執行。負責清理資源。

範例:計時器 Context Manager

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self  # 可以回傳物件本身

    def __exit__(self, exc_type, exc_value, traceback):
        self.end = time.time()
        print(f"執行時間: {self.end - self.start:.4f} 秒")
        # 如果回傳 True,會壓制 (Suppress) 區塊內的異常
        # 這裡回傳 None (預設),異常會正常拋出

# 使用自訂的 Timer
with Timer():
    # 模擬耗時工作
    sum(range(10000000))

# 輸出: 執行時間: 0.2xxx 秒

這比使用裝飾器計時更加靈活,因為我們可以針對任意程式碼區塊計時,而不僅限於函數。

範例:資料庫連線 (模擬)

class DatabaseConnection:
    def __enter__(self):
        print("連線資料庫...")
        return "DB_CONNECTION_OBJECT"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("關閉連線。")
        if exc_type:
            print(f"發生錯誤: {exc_val}")
        # 不回傳 True,讓錯誤繼續往上拋

with DatabaseConnection() as db:
    print(f"正在使用 {db}")
    # raise ValueError("Oops!") # 測試錯誤發生

3. contextlib 模組:更簡單的寫法

如果你覺得寫一個 Class 太麻煩,contextlib.contextmanager 裝飾器讓你用 Generator 函數就能寫出 Context Manager。

from contextlib import contextmanager

@contextmanager
def my_open(filename, mode):
    f = open(filename, mode)
    try:
        yield f  # yield 前是 __enter__
    finally:
        f.close() # yield 後是 __exit__

with my_open("test.txt", "w") as f:
    f.write("Hello Contextlib")

這段程式碼背後利用了 yield 暫停執行的特性,將 try...finally 封裝起來。

4. 常見應用場景

除了檔案和資料庫,Context Manager 還有很多用途:

  • threading.Lock: 自動上鎖與解鎖。
    with lock:
        # Critical Section
        pass
    
  • decimal.localcontext: 暫時改變浮點數精確度。
  • unittest.assertRaises: 測試是否拋出預期異常。

5. 總結

Context Manager 是管理 Setup (設定) 與 Teardown (清理) 邏輯的最佳解法。

核心觀念:

  • with 確保 __exit__ 永遠被執行。
  • __enter__ 準備資源。
  • __exit__ 釋放資源並處理異常。
  • contextlib 提供了簡化寫法。

下一章,我們將進入 Batch 4 的壓軸——設計模式 (Design Patterns),看看如何在 Python 中實現 Singleton, Factory 與 Observer 等經典模式!


延伸閱讀

LATEST POST
TAG