上下文管理器的作用
很多情況,當我們使用完乙個資源後,我們需要手動的關閉掉它,比如操作檔案,建立資料庫連線等。但是,在使用資源的過程中,如果遇到異常,很可能錯誤被直接丟擲,導致來不及關閉資源。所以在大部分程式語言裡,我們使用」try-finally」語句來確保資源會關閉。比如下面的python寫檔案**:
1
2
3
4
5
try:
f=open('test.txt','a+')
f.write('foo\n')
finally:
f.close()
這樣做固然沒有問題,但是當」try-finally」中間的邏輯複雜,而且還帶有各種巢狀的話,**就很不容易維護。python的with語句,可以說功能同上面的」try-finally」幾乎一樣,但**看上去簡潔的多,我們來實現同樣的功能:
1
2
withopen('test.txt','a+')asf:
f.write('foo\n')
with語句後面跟著open()方法,如果它有返回值的話,可以使用as語句將其賦值給f。在with語句塊退出時,」f.close()」方法會自動被呼叫,即使」f.write()」出現異常,也能確保close()方法被呼叫。
自定義類來使用上下文管理器
上例中」open()」方法是python自帶的,那我們怎麼定義自己的型別來使用with語句呢。其實只要你的類定義了」__enter__()」和」__exit__()」方法,就可以使用python的上下文管理器了。」__enter__()」方法會在with語句進入時被呼叫,其返回值會賦給as關鍵字後的變數;而」__exit__()」方法會在with語句塊退出後自動被呼叫。
我們來實現個跟上節一樣的檔案寫入功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
classopenfiledemo(object):
def__init__(self,filename):
self.filename=filename
def__enter__(self):
self.f=open(self.filename,'a+')
returnself.f
def__exit__(self,exc_type,exc_val,exc_tb):
self.f.close()
withopenfiledemo('test.txt')asf:
f.write('foo\n')
異常處理
肯定有朋友注意到上面的」__exit__()」帶了三個引數,是的,他們是用來異常處理的。大部分情況下,我們希望with語句中遇到的異常最後被丟擲,但也有時候,我們想處理這些異常。」__exit__()」方法中的三個引數exc_type, exc_val, exc_tb分別代表異常型別,異常值,和異常的traceback。當你處理完異常後,你可以讓」__exit__()」方法返回true,此時該異常就會不會再被丟擲。比如我們將上例中的」__exit__()」方法改一下:
1
2
3
4
5
def__exit__(self,exc_type,exc_val,exc_tb):
self.f.close()
ifexc_type!=syntaxerror:
returntrue
returnfalse# only raise exception when syntaxerror
現在,如果遇到syntaxerror的話,異常會被正常丟擲,而其他異常的話都會被忽略。
contextlib模組
python中還有乙個contextlib模組提供一些簡便的上下文管理器功能。
closing()方法
如果說with語句塊在退出時會自動呼叫」__exit__()」方法的話,那用了」contextlib.closing()」的with語句塊則在退出時會自動呼叫」close()」方法。看一下示例:
1
2
3
4
5
6
7
8
9
10
11
importcontextlib
classresource(object):
defopen(self):
print'open resource'
defclose(self):
print'close resource'
withcontextlib.closing(resource())asr:
r.open()
程式執行後,會列印出
open resource說明resource類建立的物件被賦給了as關鍵字後面的變數r,而with語句塊退出時,自動呼叫了」r.close()」方法。close resource
contextmanager裝飾器
「@contextlib.contextmanager」是乙個裝飾器,由它修飾的方法會有兩部分構成,中間由yield關鍵字分開。由此方法建立的上下文管理器,在**塊執行前會先執行yield上面的語句;在**塊執行後會再執行yield下面的語句。看個例子比較容易明白:
1
2
3
4
5
6
7
8
9
10
11
12
13
importcontextlib
importtime
@contextlib.contextmanager
deftimeit():
start=time.time()
yield
end=time.time()
usedtime=(end-start)*1000
print'use time %d ms'%usedtime
withtimeit():
time.sleep(1)
這個」timeit()」方法實現了乙個計時器,它會計算由他生成的with語句塊執行時間。可以看出,yield上面的語句就如同之間介紹過的」__enter__()」方法,而yield下面的語句就如同」__exit__()」方法。而yield部分就是with語句塊中的**。如果yield後面帶引數的話,我們就可以用as關鍵字賦值給後面的變數,比如上例:
1
2
3
4
5
6
7
8
9
@contextlib.contextmanager
deftimeit():
start=time.time()
yieldstart
#...
withtimeit()asstarttime:
printstarttime
#...
需要注意的是,」@contextlib.contextmanager」不像之前介紹的」__exit__()」方法,遇到異常也會執行。也就是with語句塊丟擲異常的話,yield後面的**將不會被執行。所以,必要時你需要對yield語句使用」try-finally」。
posted @
2017-01-11 15:35
ld1977 閱讀(
...)
編輯收藏
python上下文管理
重寫 enter 方法,返回值會被 as 捕獲 重寫 exit 方法 democlass mycontextmanager def enter self print 進入管理器範圍執行方法 此處的返回值會被 with xx as 捕獲 return 退出上下文管理範圍執行方法,即使中間報錯也會執行 ...
python 高階 with 上下文管理
with 上下文管理器 語法糖 python 提供的一種簡化語法,在編寫 時更加簡潔 with 就是眾多語法糖中的一種 with 執行原理 能通過with進行執行的語句,都是實現了上下文管理器 上下文管理器中包含兩個魔法方法 enter 和 exit enter 方法提供環境的初始化操作 exit ...
python 上下文管理協議
class open def init self,name self.name name def enter self print 出現with語句,物件的 enter 被觸發,有返回值則賦值給as宣告的變數 return self def exit self,exc type,exc val,ex...