python 上下文管理器及自定義原理解析
這篇文章主要介紹了python 上下文管理器原理解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
Python 提供了 with 語法用于簡化資源操作的后續(xù)清除操作,是 try/finally 的替代方法,實現(xiàn)原理建立在上下文管理器之上。
Python 提供了一個 contextmanager 裝飾器,更進一步簡化上下管理器的實現(xiàn)方式。
上下文管理器是Python2.5之后才出現(xiàn)的概念。上下文管理器規(guī)定了某個對象的使用范圍,當(dāng)進入或者離開了使用范圍,都會有相應(yīng)的一些調(diào)用,比如代碼塊開始時執(zhí)行一些準備,代碼塊結(jié)束時結(jié)束一些操作。它更多的是用于資源的分配和釋放上,即在開始時分配資源,結(jié)束時釋放一些資源。比如在執(zhí)行數(shù)據(jù)庫查詢時要建立連接,查詢結(jié)束后要釋放連接;寫文件時要先打開文件,寫結(jié)束后,要關(guān)閉文件等等。還有,就是資源的加鎖和解鎖,比如在使用多線程時,可能會用到加鎖和解鎖。
上下文管理器可以通過使用更可讀、更精簡的代碼實現(xiàn)資源的分配與釋放。
復(fù)制代碼
with的使用
對于上下文管理器的使用,最常見的是使用with語句,with語句可構(gòu)建資源的分配與釋放的語法糖。
因為with語句就是為支持上下文管理器而存在的,使用上下文管理協(xié)議的方法包裹一個代碼塊(with語句體)的執(zhí)行,并為try...except...finally提供了一個方便使用的封裝。
一般語法:
def load_data(filename): f = file(filename,'w') try: f.write('test file') finally: f.close()
使用with:
# 使用with with open('test.txt', 'w') as f: f.write('Python')
通過 with 語句在編寫代碼時,會使代碼變得更加簡潔,不用再去關(guān)閉文件。
我們并不需要寫文件的關(guān)閉操作,文件會在使用完后自動關(guān)閉。
with的執(zhí)行原理
實際上,在文件操作時,并不是不需要寫文件的關(guān)閉,而是文件的關(guān)閉操作在 with 的上下文管理器中的協(xié)議方法里已經(jīng)寫好了。當(dāng)文件操作執(zhí)行完成后, with語句會自動調(diào)用上下文管理器里的關(guān)閉語句來關(guān)閉文件資源。
上下文管理協(xié)議(context management protocol)
ContextManager ,上下文是 context 直譯的叫法,在程序中用來表示代碼執(zhí)行過程中所處的前后環(huán)境。
上下文管理器中有 __enter__ 和 __exit__ 兩個方法,以with為例子,__enter__ 方法會在執(zhí)行 with 后面的語句時執(zhí)行,一般用來處理操作前的內(nèi)容。比如一些創(chuàng)建對象,初始化等;__exit__ 方法會在 with 內(nèi)的代碼執(zhí)行完畢后執(zhí)行,一般用來處理一些善后收尾工作,比如文件的關(guān)閉,數(shù)據(jù)庫的關(guān)閉等。
上下文管理協(xié)議包括兩個方法:
contextmanager.__enter__() 從該方法進入運行時上下文,并返回當(dāng)前對象或者與運行時上下文相關(guān)的其他對象。如果with語句有as關(guān)鍵詞存在,返回值會綁定在as后的變量上。
contextmanager.__exit__(exc_type, exc_val, exc_tb) 退出運行時上下文,并返回一個布爾值標示是否有需要處理的異常。如果在執(zhí)行with語句體時發(fā)生異常,那退出時參數(shù)會包括異常類型、異常值、異常追蹤信息,否則,3個參數(shù)都是None。
with語句的語法如下:
with EXPR as VAR: BLOCK
with和as是關(guān)鍵詞,EXPR就是上下文表達式,是任意表達式(一個表達式,不是表達式列表),VAR是賦值的目標變量。"as VAR"是可選的。
上述語句的底層實現(xiàn)可以這樣描述:
mgr = (EXPR) exit = type(mgr).__exit__ # 并沒有調(diào)用 value = type(mgr).__enter__(mgr) exc = True try: try: VAR = value # 如果有"as VAR" BLOCK except: # 這里會處理異常 exc = False if not exit(mgr, *sys.exc_info()): raise # 如果__exit__返回值是false,異常將被傳播;如果返回值是真,異常將被終止 finally: if exc: exit(mgr, None, None, None)
這樣with語句的執(zhí)行過程就很清楚了。
- 執(zhí)行上下文表達式,獲取上下文管理器
- 加載上下文管理器的__exit__()方法以備后期調(diào)用
- 調(diào)用上下文管理器的__enter__()方法
- 如果with語句有指定目標變量,將從__enter__()方法獲取的相關(guān)對象賦值給目標變量
- 執(zhí)行with語句體
- 調(diào)用上下文管理器的__exit__()方法,如果是with語句體造成的異常退出,那異常類型、異常值、異常追蹤信息將被傳給__exit__(),否則,3個參數(shù)都是None。
也可以將多個表達式組織在一起。
with A() as a, B() as b: BLOCK
它等價于
with A() as a: with B() as b: BLOCK
注:多上下文表達式是從python 2.7開始支持的
自定義上下文管理器(模擬with打開文件)
要實現(xiàn)一個自定義的上下文管理器,肯定要實現(xiàn)兩個方法,一是進入對象范圍時的準備工作,二是離開對象范圍時的結(jié)束工作。
Python提供了兩個類的方法分別實現(xiàn)上述功能:
- __enter__ 進入對象范圍時(一般代碼塊開始)被調(diào)用;
- __exit__ 離開對象范圍時(代碼塊結(jié)束)唄調(diào)用;
因此,一個Python類,只要實現(xiàn)了上述兩種方法,就可以說是一個上下文管理器。
class MyOpen(object): def __init__(self,path,mode): # 記錄要操作的文件路徑和模式 self.__path = path self.__mode = mode def __enter__(self): print('代碼執(zhí)行到了__enter__......') # 打開文件 self.__handle = open(self.__path,self.__mode) # 返回打開的文件對象引用, 用來給 as 后的變量f賦值 return self.__handle # 退出方法中,用來實現(xiàn)善后處理工作 def __exit__(self, exc_type, exc_val, exc_tb): print('代碼執(zhí)行到了__exit__......') self.__handle.close() # a+ 打開一個文件用于讀寫。如果該文件已存在,文件指針將會放在文件的結(jié)尾。文件打開時會是追加模式。如果該文件不存在,創(chuàng)建新文件用于讀寫。 with MyOpen('test.txt','a+') as f: # 創(chuàng)建寫入文件 f.write("Hello Python!!!") print("文件寫入成功")
通過執(zhí)行順序,可以看到文件寫入操作執(zhí)行完之后,自動調(diào)用了__exit__方法,做了善后處理工作。
代碼執(zhí)行到了__enter__...... 文件寫入成功 代碼執(zhí)行到了__exit__......
__exit__方法的參數(shù)
__exit__ 方法中有三個參數(shù),用來接收處理異常,如果代碼在運行時發(fā)生異常,異常會被保存到這里。
exc_type : 異常類型
exc_val : 異常值
exc_tb : 異?;厮葑粉?/p>
# 編寫兩個數(shù)做除法的程序,然后給除數(shù)穿入0 class MyCount(object): # 接收兩個參數(shù) def __init__(self,x, y): self.__x = x self.__y = y # 返回一個地址(實質(zhì)是被as后的變量接收),實例對象就會執(zhí)行MyCount中的方法:div() def __enter__(self): print('代碼執(zhí)行到了__enter__......') return self def __exit__(self, exc_type, exc_val, exc_tb): print("代碼執(zhí)行到了__exit__......") if exc_type == None: print('程序沒問題') else: print('程序有問題,如果你能你看懂,問題如下:') print('Type: ', exc_type) print('Value:', exc_val) print('TreacBack:', exc_tb) # 返回值決定了捕獲的異常是否繼續(xù)向外拋出 # 如果是 False 那么就會繼續(xù)向外拋出,程序會看到系統(tǒng)提示的異常信息 # 如果是 True 不會向外拋出,程序看不到系統(tǒng)提示信息,只能看到else中的輸出 return True def div(self): print("代碼執(zhí)行到了除法div") return self.__x / self.__y with MyCount(1, 0) as mc: mc.div()
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python腳本實現(xiàn)代碼行數(shù)統(tǒng)計代碼分享
這篇文章主要介紹了Python腳本實現(xiàn)代碼行數(shù)統(tǒng)計代碼分享,本文給出了實現(xiàn)代碼和使用方法及統(tǒng)計效果,需要的朋友可以參考下2015-03-03Python操作SQLite/MySQL/LMDB數(shù)據(jù)庫的方法
這篇文章主要介紹了Python操作SQLite/MySQL/LMDB數(shù)據(jù)庫的方法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11Python Pydantic進行數(shù)據(jù)驗證的方法詳解
在 Python 中,有許多庫可用于數(shù)據(jù)驗證和處理,其中一個流行的選擇是 Pydantic,下面就跟隨小編一起學(xué)習(xí)一下Pydantic 的基本概念和用法吧2024-01-01