Python上下文管理器with塊及@contextmanager的使用
上下文管理器和with塊
上下文管理器對象存在的目的是為了管理with塊,就像迭代器的存在是為了管理for循環(huán)一樣
with 語句存在的意義是對一些常用的 try/finally 結(jié)構(gòu)予以簡化。這種結(jié)構(gòu)能夠保障一段代碼在運行完成后實施某項操作,就算該段代碼因為 return 語句、異常或者 sys.exit() 調(diào)用而停止,也依舊執(zhí)行指定的操作。finally 子句中的代碼往往用于釋放重要的資源,亦或是還原臨時變動的狀態(tài)。
上下文管理器接口包含了__enter__以及__exit__這兩個方法。當 with 語句開始執(zhí)行的時候,Python 會在上下文管理器對象上啟用__enter__方法。待 with 塊運行完畢,或者因某些緣由終止之后,Python 會在上下文管理器對象上調(diào)用__exit__方法。
- __enter()__方法:當進入
with語句時,__enter__()方法會被調(diào)用。該方法通常用于執(zhí)行一些準備工作,并返回一個資源對象,這個對象會被綁定到as后的變量名上。 - __exit()__方法:當
with語句塊執(zhí)行結(jié)束時,無論是否發(fā)生了異常,__exit__()方法都會被調(diào)用。該方法通常用于執(zhí)行一些清理操作,比如關(guān)閉文件、釋放資源等。如果在with語句塊中發(fā)生了異常,異常信息會作為參數(shù)傳遞給__exit__()方法。
下面是一個精心設(shè)計的上下文管理器執(zhí)行操作案例
import sys
class LookingGlass:
def __enter__(self):
# 將原始的 sys.stdout.write 函數(shù)保存起來
self.original_wirte = sys.stdout.write
# 將 sys.stdout.write 函數(shù)替換為 reverse_write 方法
sys.stdout.write = self.reverse_wirte
# 返回一個字符串作為上下文管理器的上下文對象
return 'QWERTYUIOP'
def reverse_wirte(self, text):
# 對傳入的文本進行反轉(zhuǎn)后輸出
return self.original_wirte(text[::-1])
def __exit__(self, exc_type, exc_val, exc_tb): # 如果一切正常,那么Python 調(diào)?__exit__ ?法時傳?的參數(shù)是None, None, None
# 在退出上下文管理器時,恢復(fù)原始的 sys.stdout.write 函數(shù)
sys.stdout.write = self.original_wirte
# 如果發(fā)生的異常是 ZeroDivisionError,輸出相應(yīng)提示信息
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
return True # 返回True,告訴解釋器異常已處理
if __name__ == '__main__':
with LookingGlass() as what:
# print(obj)實質(zhì)就是調(diào)用sys.stdout.write(obj+'\n')
print("床前明月光") # 光月明前床
print("疑是地上霜") # 霜上地是疑
print(what) # POIUYTREWQ
print(what) # QWERTYUIOP 處理上下文管理器,輸出的文本正常,不再翻轉(zhuǎn)
解釋器調(diào)用__enter__方法時,除了隱式的self,不需要傳入其他參數(shù),傳給__exit__方法的三個參數(shù)如下:
- exc_type:異常類。
- exc_value:異常實例。有時候會有參數(shù)傳遞給異常構(gòu)造函數(shù),例如錯誤消息,這些參數(shù)能夠通過 exc_value.args獲取。
- exc_tb:trackback對象。
@contextmanager的用法
@contextmanager是一個十分實用且巧妙的工具。他將Python3個功能結(jié)合在一起:函數(shù)裝飾器,生成器以及上下文管理器。
使用@contextmanager能減少創(chuàng)建上下文管理器的樣板代碼,因為不用編寫一個類來定義__enter__和__exit__方法,而只要實現(xiàn)一個含有yield的生成器,來生成想要__enter__方法返回的值。
在使用@contextmanager裝飾的生成器中,yiled把函數(shù)主體分成兩部分,yield前面的代碼在with塊開始時(解釋器調(diào)用__enter__方法時)執(zhí)行。yield后面的代碼在with塊結(jié)束時(解釋器調(diào)用__exit__方法時)執(zhí)行。
繼續(xù)上面的示例:
import sys
import contextlib
@contextlib.contextmanager
def looking_glass():
original_text = sys.stdout.write
def reverse_write(text):
original_text(text[::-1])
sys.stdout.write = reverse_write
msg = ''
# 這里處理異常是因為如果with塊有異常拋出,Python會將其捕獲,然后再looking_glass函數(shù)的yield表達式再次拋出
# 但是那里又沒有處理錯誤的代碼,因此looking_glass函數(shù)將終止執(zhí)行,永遠無法恢復(fù)成原來的sys.stdout.write方法,導(dǎo)致系統(tǒng)處于無效狀態(tài)。
try:
yield 'QWERTYUIOP'
except ZeroDivisionError:
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_text
if msg:
print(msg)
if __name__ == '__main__':
with looking_glass() as what:
print("床前明月光") # 光月明前床
print("疑是地上霜") # 霜上地是疑
print(what) # POIUYTREWQ
print(what) # QWERTYUIOP其實@contextlib.contextmanager把函數(shù)包裝成了實現(xiàn)__enter__和__exit__的類。
那個類的__enter__方法:
- 調(diào)用生成器函數(shù),獲取生成器對象,姑且稱之為gen。
- 調(diào)用next(gen),驅(qū)動生成器對象執(zhí)行到y(tǒng)ield關(guān)鍵字位置。
- 返回next(gen)產(chǎn)生的值,以便把產(chǎn)生的值綁定到as語句的目標變量上。
with語句終止,__exit__方法有以下作用:
- 檢查有沒有異常傳給exc_type,如果有則調(diào)用gen.throw(exception) ,在生成器函數(shù)主體的yield關(guān)鍵字所在的行拋出異常。
- 否則調(diào)用next(gen),恢復(fù)執(zhí)行生成器函數(shù)主體中yiled后面的代碼。
因此在使用@contextmanager裝飾器是,應(yīng)該把yield放在try/finally語句中,這是不可避免的,因為我們永遠不知道上下文管理器的用戶會在with塊中做什么。
@contextmanager還有一個鮮為人知的功能:它裝飾的生成器也可以用做裝飾器。例如:
@looking_glass()
def varse():
print("舉頭望明月低頭思故鄉(xiāng)")
if __name__ == '__main__':
varse() # 鄉(xiāng)故思頭低月明望頭舉總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
利用Python批量循環(huán)讀取Excel的技巧分享
這篇文章主要為大家詳細介紹了何用Python批量循環(huán)讀取Excel,文中的示例代碼講解詳細,對我們的學(xué)習(xí)或工作有一定的幫助,感興趣的可以了解一下2023-07-07
Django中select_related查詢優(yōu)化的具體實現(xiàn)
本文介紹了Django ORM中的N+1查詢問題及其解決方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-12-12
python基于plotly實現(xiàn)畫餅狀圖代碼實例
這篇文章主要介紹了python基于plotly實現(xiàn)畫餅狀圖代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12
在linux系統(tǒng)下安裝python librtmp包的實現(xiàn)方法
今天小編就為大家分享一篇在linux系統(tǒng)下安裝python librtmp包的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07

