上下文管理器與裝飾器類似,它們都是包裝其他代碼的工具。但裝飾器用于包裝定義的代碼塊(如函數(shù)或類),而上下文管理器可以包裝任意格式的代碼塊。
有一些任務(wù),可能事先需要設(shè)置,事后做清理工作。這種時候用上下文管理器就可以相當(dāng)方便地進(jìn)行處理。
例如,在對文件進(jìn)行讀寫操作時,通常得這樣進(jìn)行
try:
file = open("/tmp/foo.txt")
data = file.read()
finally:
file.close()
這樣的寫法雖然沒有問題,但總是需要手動關(guān)閉文件,很可能忘記關(guān)閉文件句柄,引起漏洞。
在python2.5后新增了關(guān)鍵字with,使用with語句即可進(jìn)入上下文管理器,它可以在執(zhí)行完代碼之后自動幫我們關(guān)閉文件,因此上述代碼可改寫成:
with open('test.txt') as f:
data = f.read()
with究竟是如何工作的,或者說上面提到的上下文管理器究竟是什么?
with語句的作用實(shí)際上就是對其后的代碼求值(本例中即為調(diào)用open函數(shù))。該表達(dá)式返回一個對象,該對象包含兩個特殊方法:__enter__()和__exit__()。
- 緊跟with后面的語句被求值后,返回對象的__enter__()方法被調(diào)用,這個方法的返回值將被賦值給as后面的變量。當(dāng)with后面的代碼塊全部被執(zhí)行完之后,將調(diào)用前面返回對象的__exit__()方法。
我們可以通過如下實(shí)例來看
class Sample:
def __enter__(self):
print("__enter__")
return "sample"
def __exit__(self, type, value, trace):
print("__exit__")
def get_sample():
return Sample()
with get_sample() as sample:
print(sample)
>>>
__enter__
sample
__exit__
整個過程有如下幾步:
- __enter__()方法被執(zhí)行
- __enter__()方法返回的值賦值給as后的變量sample
- 執(zhí)行代碼塊
- __exit__()方法被調(diào)用
可以看出,with語句是進(jìn)入上下文管理器的入口且默認(rèn)執(zhí)行__enter__函數(shù),而退出上下文管理器時默認(rèn)執(zhí)行__exit__函數(shù)。
由上我們可以知道,打開和關(guān)閉資源(如文件和數(shù)據(jù)庫連接)是編寫上下文管理器的一個重要應(yīng)用。但除此之外,上下文管理器還有另一個重要功能:
異常處理
with語句的表達(dá)式的作用是返回一個遵循特定協(xié)議的對象,該對象必須定義一個 __enter__方法和一個__exit__方法。
除了self參數(shù),__enter__方法不接受任何參數(shù),如果有as變量(as子句是可選項(xiàng) ),返回值賦給as后的變量。
除了self參數(shù),__exit__方法還帶有三個位置參數(shù):
- exc_type:一個異常類型
- exc_instance :一個異常實(shí)例
- traceback :一個回溯
無異常時它們?nèi)珵镹one,但如果在代碼塊內(nèi)有異常發(fā)生,則參數(shù)被填充
上下文管理器必須定義__exit__方法,該方法可以選擇性地處理包裝代碼塊中出現(xiàn)的異常,或者處理其他需要關(guān)閉上下文管理器狀態(tài)的事情。如果exit方法接收一個異常,它可以
- 返回False實(shí)現(xiàn)異常的傳播
- 返回True終止異常
- 拋出一個不同的異常,它將替代異常被發(fā)送出去