專項練習 · 9 分鐘閱讀

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 只能用於檔案嗎?

不是。任何實現上下文管理器協議的物件都可以使用,包括鎖、資料庫事務和測試夾具。

上下文管理器可以抑制例外嗎?

可以,只要 __exit__ 回傳真值。應謹慎使用,並把行為寫進文件。

如果 __enter__ 拋出例外會怎樣?

主體不會進入,__exit__ 也不會被呼叫。例外會傳播給呼叫方。

練習上下文管理器

12 道題,覆蓋 with、__enter__、__exit__、清理和 contextlib。

打開練習

繼續閱讀: