上下文管理器與裝飾器類似窒盐,它們都是包裝其他代碼的工具。但裝飾器用于包裝定義的代碼塊(如函數(shù)或類)钢拧,而上下文管理器可以包裝任意格式的代碼塊蟹漓。
有一些任務(wù),可能事先需要設(shè)置源内,事后做清理工作葡粒。這種時候用上下文管理器就可以相當(dāng)方便地進行處理。
例如膜钓,在對文件進行讀寫操作時嗽交,通常得這樣進行
try:
file = open("/tmp/foo.txt")
data = file.read()
finally:
file.close()
這樣的寫法雖然沒有問題,但總是需要手動關(guān)閉文件颂斜,很可能忘記關(guān)閉文件句柄夫壁,引起漏洞。
在python2.5后新增了關(guān)鍵字with沃疮,使用with語句即可進入上下文管理器盒让,它可以在執(zhí)行完代碼之后自動幫我們關(guān)閉文件,因此上述代碼可改寫成:
with open('test.txt') as f:
data = f.read()
with究竟是如何工作的司蔬,或者說上面提到的上下文管理器究竟是什么邑茄?
with語句的作用實際上就是對其后的代碼求值(本例中即為調(diào)用open函數(shù))。該表達式返回一個對象俊啼,該對象包含兩個特殊方法:__enter__()和__exit__()肺缕。
- 緊跟with后面的語句被求值后,返回對象的__enter__()方法被調(diào)用授帕,這個方法的返回值將被賦值給as后面的變量同木。當(dāng)with后面的代碼塊全部被執(zhí)行完之后,將調(diào)用前面返回對象的__exit__()方法豪墅。
我們可以通過如下實例來看
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語句是進入上下文管理器的入口且默認執(zhí)行__enter__函數(shù),而退出上下文管理器時默認執(zhí)行__exit__函數(shù)偶器。
由上我們可以知道斩萌,打開和關(guān)閉資源(如文件和數(shù)據(jù)庫連接)是編寫上下文管理器的一個重要應(yīng)用。但除此之外屏轰,上下文管理器還有另一個重要功能:
異常處理
with語句的表達式的作用是返回一個遵循特定協(xié)議的對象颊郎,該對象必須定義一個 __enter__方法和一個__exit__方法。
除了self參數(shù)霎苗,__enter__方法不接受任何參數(shù)姆吭,如果有as變量(as子句是可選項 ),返回值賦給as后的變量唁盏。
除了self參數(shù)内狸,__exit__方法還帶有三個位置參數(shù):
- exc_type:一個異常類型
- exc_instance :一個異常實例
- traceback :一個回溯
無異常時它們?nèi)珵镹one,但如果在代碼塊內(nèi)有異常發(fā)生检眯,則參數(shù)被填充
上下文管理器必須定義__exit__方法,該方法可以選擇性地處理包裝代碼塊中出現(xiàn)的異常昆淡,或者處理其他需要關(guān)閉上下文管理器狀態(tài)的事情锰瘸。如果exit方法接收一個異常,它可以
- 返回False實現(xiàn)異常的傳播
- 返回True終止異常
- 拋出一個不同的異常昂灵,它將替代異常被發(fā)送出去