
在寫程式時,我們經常需要管理「資源」,例如:開啟檔案、連線資料庫、取得執行緒鎖 (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:
__enter__(self): 進入with區塊時執行。回傳值會賦予給as後面的變數。__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 passdecimal.localcontext: 暫時改變浮點數精確度。unittest.assertRaises: 測試是否拋出預期異常。
5. 總結
Context Manager 是管理 Setup (設定) 與 Teardown (清理) 邏輯的最佳解法。
核心觀念:
with確保__exit__永遠被執行。__enter__準備資源。__exit__釋放資源並處理異常。contextlib提供了簡化寫法。
下一章,我們將進入 Batch 4 的壓軸——設計模式 (Design Patterns),看看如何在 Python 中實現 Singleton, Factory 與 Observer 等經典模式!
延伸閱讀: