Python 上下文管理器練習指南
成功路徑上的清理很容易忘記,例外打斷函式時更容易漏掉。上下文管理器把準備和收尾放在程式碼區塊兩側,讓清理邏輯只有一個明確的歸屬。
用 with 成對操作
with 語句打開資源、執行程式碼區塊,並在結束時關閉資源。檔案是最典型的例子,但同樣的模式也適用於鎖、臨時目錄和事務。程式碼區塊應專注於真正需要該資源的工作。
from pathlib import Path
path = Path("pypractice-guide.txt")
path.write_text("hello\n", encoding="utf-8")
with path.open(encoding="utf-8") as handle:
print(handle.read().strip())
path.unlink()受管理的檔案
實現上下文管理器協議
__enter__ 準備資源,並回傳綁定到 as 的值。__exit__ 接收例外型別、離群值和回溯資訊,然後執行清理。回傳假值會讓例外繼續傳播;回傳真值會抑制例外。
class Guard:
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc, tb):
print("exit")
with Guard():
print("inside")準備與收尾
出錯時清理仍會執行
程式碼區塊不必為每個資源操作都套一層 try/finally。即使主體拋出例外,__exit__ 也會在例外繼續傳播前被呼叫。這正是 with 對正確性的價值,而不只是寫法更短。
class Guard:
def __enter__(self):
return self
def __exit__(self, *args):
print("cleaned")
try:
with Guard():
raise ValueError("boom")
except ValueError:
print("caught")例外逃出前先清理
用 contextmanager 構造
用 contextlib.contextmanager 裝飾生成器。yield 之前是準備,yield 之後是清理,yield 出來的值可以通過 as 獲取。保留且只保留一個 yield,並把清理放在它後面。
from contextlib import contextmanager
@contextmanager
def label(name):
print("start", name)
yield name
print("end", name)
with label("demo") as value:
print("body", value)基於生成器的管理器
不要意外吞掉例外
從 __exit__ 回傳 True 會抑制例外。對明確的策略可能有幫助,但也可能掩蓋錯誤。呼叫方應該看到錯誤時,回傳 None 或 False。任何抑制行為都要寫清楚。
class Visible:
def __enter__(self):
return self
def __exit__(self, *args):
return False
with Visible():
print("still visible")讓錯誤繼續傳播
你的練習清單
- 準備和清理屬於一起時使用 with。
- 從 __enter__ 回傳真正有用的物件。
- 把清理放在 __exit__ 中,而不是只處理成功路徑。
- 小型生成器管理器可以用 contextlib.contextmanager。
- 除非有意抑制,否則回傳 False 或 None。
常見問題
with 只能用於檔案嗎?
不是。任何實現上下文管理器協議的物件都可以使用,包括鎖、資料庫事務和測試夾具。
上下文管理器可以抑制例外嗎?
可以,只要 __exit__ 回傳真值。應謹慎使用,並把行為寫進文件。
如果 __enter__ 拋出例外會怎樣?
主體不會進入,__exit__ 也不會被呼叫。例外會傳播給呼叫方。
練習上下文管理器
12 道題,覆蓋 with、__enter__、__exit__、清理和 contextlib。
繼續閱讀: