【Python】with及上下文管理器的原理和應用
這篇部落格主要總結with用法,自定義上下文管理器,以及__exit__的引數相關內容。
with 語句是 Pyhton 提供的一種簡化語法,適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的“清理”操作,釋放資源,with 語句主要是為了簡化程式碼操作。
with:檔案使用後自動關閉
# 建立一個檔案test.txt,若存在則開啟,寫入Hello Python # 建立/開啟檔案 f = open('test.txt', 'w') f.write("Hello Python") # 關閉這個檔案 f.close() # 使用with with open('test.txt', 'w') as f: f.write('Python')
可以發現:通過 with 語句在編寫程式碼時,會使程式碼變得更加簡潔,不用再去關閉檔案。
with的執行過程:
在執行 with 語句時,首先執行 with 後面的 open 程式碼
執行完程式碼後,會將程式碼的結果通過 as 儲存到 f 中
然後在下面實現真正要執行的操作
在操作後面,並不需要寫檔案的關閉操作,檔案會在使用完後自動關閉
with的執行原理
實際上,在檔案操作時,並不是不需要寫檔案的關閉,而是檔案的關閉操作在 with 的上下文管理器中的協議方法裡已經寫好了。當檔案操作執行完成後, with語句會自動呼叫上下文管理器裡的關閉語句來關閉檔案資源。
上下文管理器
ContextManager ,上下文是 context 直譯的叫法,在程式中用來表示程式碼執行過程中所處的前後環境。上下文管理器中有 __enter__ 和 __exit__ 兩個方法,以with為例子,__enter__ 方法會在執行 with 後面的語句時執行,一般用來處理操作前的內容。比如一些建立物件,初始化等;__exit__ 方法會在 with 內的程式碼執行完畢後執行,一般用來處理一些善後收尾工作,比如檔案的關閉,資料庫的關閉等。
自定義一個上下文管理器,模擬with檔案操作 class MyOpen(object): def __init__(self,path,mode): # 記錄要操作的檔案路徑和模式 self.__path = path self.__mode = mode def __enter__(self): print('程式碼執行到了__enter__......') # 開啟檔案 self.__handle = open(self.__path,self.__mode) # 返回開啟的檔案物件引用, 用來給 as 後的變數f賦值 return self.__handle # 退出方法中,用來實現善後處理工作 def __exit__(self, exc_type, exc_val, exc_tb): print('程式碼執行到了__exit__......') self.__handle.close() # a+ 開啟一個檔案用於讀寫。如果該檔案已存在,檔案指標將會放在檔案的結尾。檔案開啟時會是追加模式。如果該檔案不存在,建立新檔案用於讀寫。 with MyOpen('test.txt','a+') as f: # 建立寫入檔案 f.write("Hello Python!!!") print("檔案寫入成功")
執行結果:
通過執行順序,可以看到檔案寫入操作執行完之後,自動呼叫了__exit__方法,做了善後處理工作。
__exit__方法的引數
__exit__ 方法中有三個引數,用來接收處理異常,如果程式碼在執行時發生異常,異常會被儲存到這裡。
exc_type : 異常型別
exc_val : 異常值
exc_tb : 異常回溯追蹤
# 編寫兩個數做除法的程式,然後給除數穿入0
class MyCount(object):
# 接收兩個引數
def __init__(self,x, y):
self.__x = x
self.__y = y
# 返回一個地址(實質是被as後的變數接收),例項物件就會執行MyCount中的方法:div()
def __enter__(self):
print('程式碼執行到了__enter__......')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("程式碼執行到了__exit__......")
if exc_type == None:
print('程式沒問題')
else:
print('程式有問題,如果你能你看懂,問題如下:')
print('Type: ', exc_type)
print('Value:', exc_val)
print('TreacBack:', exc_tb)
# 返回值決定了捕獲的異常是否繼續向外丟擲
# 如果是 False 那麼就會繼續向外丟擲,程式會看到系統提示的異常資訊
# 如果是 True 不會向外丟擲,程式看不到系統提示資訊,只能看到else中的輸出
return True
def div(self):
print("程式碼執行到了除法div")
return self.__x / self.__y
with MyCount(1, 0) as mc:
mc.div()
執行結果:
可以看到,系統沒有丟擲異常,而是__exit__捕獲到了異常,並按照我們的方式進行了丟擲。