上下文管理器-1.基本概念及其實現
來自專欄 python菜籃
概要
本文是上下文管理器專題第一篇,主要簡單介紹上下文及上下文管理器的基本概念,分析with操作符的使用及其優缺點,最後還介紹了如何實現自定義上下文管理器
什麼是上下文
程序中所謂的上下文件就是指程序所執行的環境狀態,或者說程序運行的情景
上下文管理器定義
寫代碼時,我們希望把一些操作放到一個代碼塊中,這樣在代碼塊中執行時就可以保持在某種運行狀態,而當離開該代碼塊時就執行另一個操作,結束當前狀態;所以,簡單來說,上下文管理器的目的就是規定對象的使用範圍,如果超出範圍就採取「處理」,這一功能是在Python3.5之後引進的,它的優勢在於可以使得你的代碼更具可讀性,且不容易出錯。
常見的上下文管理器:with操作符
最常見的上下文管理器就是with語句了,python提供了with語句語法,來構建對資源的自動創建與自動釋放,允許你在有需要的時候,精確地分配和釋放資源
使用方法:
首先我們來看一段未使用with語句的代碼,看看它在運行過程中會出現什麼問題?
#when without using context managerf = open("hl.txt", "w")print(f.closed) #運行到這裡,會列印出False,因為沒有關閉f.write("Hello,context manager!")f.close()print(f.closed) #這裡才會列印出True,表示文件關閉
表面上看似這段代碼也並沒什麼大的問題,但如果這段代碼根本執行不到f.close()這一句就會出現問題,假設我們在寫入的過程中,磁碟滿了,代碼就會在寫入的那一句出現異常,這樣就永遠執行不到f.close()這一句,文件就永遠不會被關閉,當然也可以用try…finally語句,如下所示:
#when using try…finallytry: f = open("hl.txt", "w") print(f.closed) #運行到這裡,會列印出False,因為沒有關閉 f.write("Hello,context manager!")finally: f.close()print(f.closed) #這裡才會列印出True,表示文件關閉
但是同樣存在問題,當我們執行的不是簡單的寫入操作,而是其他更複雜的操作,如拷貝,同時讀和寫,try…finally語句根本無法保證代碼的美感。這時,with就派上用場了。
下面,讓我們來看一下使用了with的情況
#when using context managerwith open("hl.txt","w") as f: print(f.closed) #False f.write("Hello,context manager!")print(f.closed) #True 無需執行f.close()
上面代碼中,可以看到一個代碼塊:with…as…
其中,with關鍵詞總是伴隨著上下文管理器出現,as關鍵詞將open(「hl.txt」,」w」)賦給了新的對象f,使得在該代碼塊中可以對f執行任意操作,代碼相當簡潔。使用with的優點:
避免了瑣碎操作:通過使用with,許多樣板代碼可以被消掉
避免了遺忘步驟:因此不用關注嵌套代碼如何退出,又能確保我們的文件會被關閉實現自定義上下文管理器
實現了上下文協議的函數/對象即為上下文管理器,上下文管理協議則是由enter和exit構成
要實現上下文管理器可以有兩種方式,一種為基於類的實現,一種為基於生成器的實現基於類的實現:
基於類的實現需要實現兩個方法:enter()和_exit_()
enter():負責進入代碼塊的準備工作,進入前被調用;exit():負責離開代碼塊的善後工作,離開後被調用;class File(object): def __init__(self, file_name, method): self.file_obj = open(file_name, method) def __enter__(self): return self.file_obj def __exit__(self, type, value, traceback): self.file_obj.close()
任何定義了_enter_()和_exit_()方法的對象都可以用於上下文管理器,通過定義enter和exit方法,我們可以在with語句里使用它
with File(demo.txt, w) as opened_file: opened_file.write(Hola!)
我們的exit函數接受三個參數。這些參數對於每個上下文管理器類中的exit方法都是必須的。我們來看看在底層都發生了什麼。
1. with語句先調用File類的__enter__方法
2. __enter__方法打開文件並返回給with語句
3. 打開的文件句柄被傳遞給opened_file參數
4. 我們使用.write()來寫文件
5. with語句調用__exit__方法關閉文件。
處理異常
如果發生異常,Python會將異常的type,value和traceback傳遞給exit方法
嘗試訪問文件對象的一個不支持的方法with File(demo.txt, w) as opened_file: opened_file.undefined_function(Hola!)
exit方法返回的是None(如果沒有return語句那麼方法會返回None)。因此,with語句拋出了那個異常。
Traceback (most recent call last): File "<stdin>", line 2, in <module>AttributeError: file object has no attribute undefined_function
當異常發生時,with語句會採取下面4步。
1. 它把異常的type,value和traceback傳遞給exit方法。 2. 它讓exit方法來處理異常 3. 如果exit返回的是True,那麼這個異常就被優雅地處理了。 4. 如果exit返回的是True以外的任何東西,那麼這個異常將被with語句拋出在exit方法中處理異常:
class File(object): def __init__(self, file_name, method): self.file_obj = open(file_name, method) def __enter__(self): return self.file_obj def __exit__(self, type, value, traceback): print("Exception has been handled") self.file_obj.close() return Truewith File(demo.txt, w) as opened_file: opened_file.undefined_function()
exit方法返回了True,因此沒有異常會被with語句拋出
基於生成器的實現:
我們還可以用裝飾器(decorators)和生成器(generators)來實現上下文管理器
Python有個contextlib模塊專門用於這個目的。我們可以使用一個生成器函數來實現一個上下文管理器,而不是使用一個類。from contextlib import contextmanager@contextmanagerdef file_open(path): try: f_obj = open(path,"w") yield f_obj except OSError: print("We had an error!") finally: print("Closing file") f_obj.close()if __name__ == "__main__": with file_open("test/test.txt") as fobj: fobj.write("Testing context managers")
在這裡,我們從contextlib模塊中引入contextmanager,然後裝飾我們所定義的file_open函數。這就允許我們使用Python的with語句來調用file_open函數。在函數中,我們打開文件,然後通過yield,將其傳遞出去,最終主調函數可以使用它。
一旦with語句結束,控制就會返回給file_open函數,它繼續執行yield語句後面的代碼。這個最終會執行finally語句--關閉文件。如果我們在打開文件時遇到了OSError錯誤,它就會被捕獲,最終finally語句依然會關閉文件句柄。
推薦閱讀:
※數據分析的python基底(4)建立數據分析的流程
※空間數據可視化筆記——simple features空間對象基礎
※黃哥Python每日新聞(2017-8-9)
※python isinstance 函數
TAG:Python |