2.24. 情境管理器¶
with 陳述式會先執行設定程式碼,接著執行主體,然後執行收尾程式碼 -- 並保證即使主體中途失敗,收尾程式碼仍會執行。提供設定與收尾的這一對方法稱為 情境管理器。
其形式為:
with <expression> as <name>:
<body>
該運算式會回傳一個情境管理器。Python 會呼叫它的 __enter__ 方法,並選擇性地將結果繫結到 <name>,接著執行主體,然後呼叫 __exit__ -- 無論主體是正常完成還是引發例外。
__exit__ 會在區塊結束時執行,無論區塊內部發生什麼事。¶
2.24.1. 使用情境管理器¶
最典型的範例是開啟檔案:
with open("data.txt") as f:
text = f.read()
# f is now closed, even if read() failed
open() 會回傳一個本身就是情境管理器的檔案物件;__enter__ 回傳該檔案,__exit__ 則將其關閉。with 區塊讓「完成後一律關閉檔案」成為預設行為,而非需要呼叫者自行記住的事。
2.24.1.1. 多個情境管理器¶
單一個 with 陳述式可以一次進入多個情境管理器,以逗號分隔:
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
這等同於兩個巢狀的 with 區塊,但更扁平。各管理器由左至右進入,並以相反順序退出;若右側管理器的 __enter__ 引發例外,左側管理器的 __exit__ 仍會執行。
2.24.2. 撰寫情境管理器¶
任何具有 __enter__ 與 __exit__ 方法的類別都可作為情境管理器使用:
class Section:
def __init__(self, label):
self.label = label
def __enter__(self):
print("---", self.label, "---")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("--- end", self.label, "---")
return False
with Section("setup"):
print("doing the work")
輸出:
--- setup ---
doing the work
--- end setup ---
__exit__ 會接收三個描述結束該區塊之例外的引數,若區塊正常完成,則為三個 None 值。回傳 False(或 None)會讓任何例外在收尾後繼續傳播;回傳 True 則會吞掉它。
對任何具有「開啟/關閉」或「取得/釋放」生命週期的資源都可使用情境管理器 -- 不限於檔案。這種模式讓清理工作與設定在兩者引入之處成對出現,因此在一個長函式中段遺漏 close 也不會導致資源外洩。