with 陳述式簡化 try/finally 資源釋放模式,確保檔案、鎖、資料庫連線等資源在正常或異常結束時都能正確釋放。本文完整說明協議介面、內建支援類型、自訂上下文管理器與巢狀用法。
# 傳統寫法
f = open('data.txt')
try:
data = f.read()
finally:
f.close()
# with 寫法 (Python 2.5+ / 2.6+ 預設啟用)
with open('data.txt') as f:
data = f.read()
# 區塊結束自動呼叫 f.close()
支援 with 的物件必須實作兩個特殊方法:
class MyContext:
def __enter__(self):
print("進入區塊")
return "resource_object" # 回傳值綁定給 as 變數
def __exit__(self, exc_type, exc_val, exc_tb):
print("離開區塊")
# exc_type/val/tb 為異常資訊,正常結束皆為 None
# 回傳 True 可吞沒異常,False/None 則繼續拋出
return False
with MyContext() as resource:
print("使用資源:", resource)
# 輸出:
# 進入區塊
# 使用資源: resource_object
# 離開區塊
class ErrorContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f"捕獲異常: {exc_type.__name__}: {exc_val}")
return True # 吞沒異常,不再傳播
return False
with ErrorContext():
raise ValueError("測試錯誤")
print("程式繼續執行") # 因為 __exit__ 回傳 True,異常被吞沒
| 類型 | __exit__ 行為 |
|---|---|
檔案物件 (open()) | 關閉檔案 |
執行緒鎖 (threading.Lock) | 釋放鎖 |
資料庫連線 (sqlite3.connect) | 提交/回滾交易 |
decimal.localcontext | 恢復精度設定 |
contextlib.closing | 呼叫 close() |
from contextlib import contextmanager
@contextmanager
def open_file(path, mode):
f = open(path, mode)
try:
yield f # yield 前為 __enter__,yield 後為 __exit__
finally:
f.close()
with open_file('test.txt', 'w') as f:
f.write('hello')
from contextlib import closing
import urllib.request
with closing(urllib.request.urlopen('https://example.com')) as page:
html = page.read()
# 自動呼叫 page.close()
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in file_list]
# 全部自動關閉,即使中間發生異常
data = [f.read() for f in files]
# Python 2.7+ / 3.1+ 支援多重上下文
with open('in.txt') as fin, open('out.txt', 'w') as fout:
for line in fin:
fout.write(line.upper())
# 等價於
with open('in.txt') as fin:
with open('out.txt', 'w') as fout:
for line in fin:
fout.write(line.upper())
import sqlite3
from contextlib import contextmanager
@contextmanager
def transaction(conn):
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
with sqlite3.connect('db.sqlite') as conn:
with transaction(conn):
conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
conn.execute("INSERT INTO users (name) VALUES (?)", ("Bob",))
# 自動 commit 或 rollback
import time
from contextlib import contextmanager
@contextmanager
def timer(label):
start = time.time()
try:
yield
finally:
print(f"{label}: {time.time() - start:.3f}s")
with timer("資料處理"):
process_large_data()
with。__enter__/__exit__ 或使用 @contextmanager。__exit__ 回傳 True 僅用於刻意吞沒特定異常,預設回傳 False。__enter__ 中執行可能失敗的複雜邏輯,若失敗 __exit__ 不會被呼叫。