函數(shù)-上下文管理器
上下文管理協(xié)議(Context Management Protocol):包含方法 enter() 和 exit(),支持該協(xié)議的對象要實現(xiàn)這兩個方法。
上下文管理器(Context Manager):支持上下文管理協(xié)議的對象,這種對象實現(xiàn)了__enter__() 和 exit() 方法。
上下文管理器定義執(zhí)行 with 語句時要建立的運行時上下文,負責(zé)執(zhí)行 with 語句塊上下文中的進入與退出操作。通常使用 with 語句調(diào)用上下文管理器,也可以通過直接調(diào)用其方法來使用。
運行時上下文(runtime context):由上下文管理器創(chuàng)建,通過上下文管理器的 enter() 和__exit__() 方法實現(xiàn),enter() 方法在語句體執(zhí)行之前進入運行時上下文,exit() 在語句體執(zhí)行完后從運行時上下文退出。with 語句支持運行時上下文這一概念。
上下文表達式(Context Expression):with 語句中跟在關(guān)鍵字 with 之后的表達式,該表達式要返回一個上下文管理器對象。
語句體(with-body):with 語句包裹起來的代碼塊,在執(zhí)行語句體之前會調(diào)用上下文管理器的 enter() 方法,執(zhí)行完語句體之后會執(zhí)行__exit__() 方法。
01 with語句
with是從Python2.5引入的一個新的語法,它是一種上下文管理協(xié)議的具體實現(xiàn),目的在于從流程圖中把 try、except 和finally 關(guān)鍵字和 資源分配釋放相關(guān)代碼統(tǒng)統(tǒng)去掉,簡化try….except….finlally的處理流程。
with通過__enter__方法初始化,然后在__exit__中做善后以及處理異常。 所以使用with處理的對象必須有__enter__()和__exit__()這兩個方法。
其中__enter__()方法在語句體(with語句包裹起來的代碼塊)執(zhí)行之前進入運行,exit()方法在語句體執(zhí)行完畢退出后運行。
with 語句適用于對資源進行訪問的場合,確保不管使用過程中是否發(fā)生異常都會執(zhí)行必要的“清理”操作,釋放資源,比如文件使用后自動關(guān)閉、線程中鎖的自動獲取和釋放等。
With語句的基本語法格式:
with expression [as target]:
with_body
'''
參數(shù)說明:
expression:是一個需要執(zhí)行的表達式;
target:是一個變量或者元組,存儲的是expression表達式執(zhí)行返回的結(jié)果,可選參數(shù)。
with_body:是一段語句體。
'''
02 應(yīng)用場景
有一些任務(wù),可能事先需要設(shè)置,事后做清理工作。對于這種場景,Python的with語句提供了一種非常方便的處理方式。一個很好的例子是文件處理,用戶需要獲取一個文件句柄,從文件中讀取數(shù)據(jù),然后關(guān)閉文件句柄。
- 文件操作
- 進程線程之間互斥對象
- 資源的加鎖和解鎖
- 支持上下文其他對象
03 示例
# 不用with語句打開文件
f = open("test.txt")
print(f.read())
f.close()
'''
這里有兩個問題:
一是可能忘記關(guān)閉文件句柄;
二是文件讀取數(shù)據(jù)發(fā)生異常,沒有進行任何處理。
'''
# 使用異常處理語句增強文件操作
try:
f = open('test.txt')
print(f.read())
except:
print('fail to open')
finally:
f.close()
'''
這里有一個問題:
雖然這段代碼運行良好,但是太冗長
'''
# 使用with上下文管理器
with open('test.txt') as f:
print(f.read())
'''
有更優(yōu)雅的語法,
還可以很好的處理上下文環(huán)境產(chǎn)生的異常。
'''
with open('test.txt') as f:
print(f.read())
print(f.closed) # 返回True,說明文件句柄已經(jīng)被自動關(guān)閉
04 with工作原理
緊跟with關(guān)鍵字后面的表達式會被求值,表達式運算之后返回一個對象,該對象必須包含兩個方法:enter()和__exit__()。表達式運算之后,會調(diào)用對象的__enter__()方法,這個方法的返回值將被賦值給as關(guān)鍵字后面的變量。
當(dāng)with包含的代碼塊全部被執(zhí)行完之后,再調(diào)用前面返回對象的__exit__()方法。
針對上面示例,當(dāng)打開文件執(zhí)行操作時,具體流程如下:
-
with語句先暫存了File類的__exit__?法
-
然后它調(diào)?File類的__enter__?法
-
__enter__?法打開?件并返回給with語句
-
打開的?件句柄被傳遞給 f 參數(shù)
-
我們使?.read()來讀取?件
-
with語句調(diào)?之前暫存的__exit__?法
-
__exit__?法關(guān)閉了?件
#示例1,演示上下文運行全流程:
class Test:
def __enter__(self):
print("開始運行上下文")
def __exit__(self, type, value, trace):
print("結(jié)束運行上下文")
with Test():
print("上下文運行中")
#示例2,如果with表達式運算結(jié)果不是上下文管理器,with將拋出異常:
class Test:
pass
with Test():
print("上下文運行中")
'''
Traceback (most recent call last):
File "d:/www_vs/test2.py", line 8, in <module>
with test():
AttributeError: __enter__
'''
#示例3,完善上下文管理器
class Test:
name = "上下文管理器"
def __enter__(self):
return self
def __exit__(self, type, value, trace):
print(type)
print(value)
print(trace)
with Test() as t:
print(t.name)
raise
'''
上下文管理器
<class 'RuntimeError'>
No active exception to reraise
<traceback object at 0x02BA7988>
Traceback (most recent call last):
File "d:/www_vs/test3.py", line 13, in <module>
raise
RuntimeError: No active exception to reraise
'''
通過示例3,可以看到__enter__()方法被執(zhí)行, enter()方法必須包含1個位置參數(shù)。該方法的返回值是this,賦值給變量t。
t表示Test的實例對象,執(zhí)行with代碼塊,打印實例對象的屬性name。然后,使用raise主動拋出異常。
exit()方法被調(diào)用。該方法必須包含4個位置參數(shù)。with真正強大之處是它可以處理異常。可能你已經(jīng)注意到__exit__方法后面三個參數(shù):type、 val 和 trace。 這些參數(shù)在異常處理中相當(dāng)有用,type表示異常類型,val表示異常的具體信息,trace表示具體錯誤細節(jié)。
實際上,如果在with后面的代碼塊拋出任何異常時,exit() 方法被執(zhí)行,與之關(guān)聯(lián)的type、value和stack trace傳給 exit() 方法,因此拋出的異常信息會被打印出來了。
在開發(fā)庫時,清理資源、 關(guān)閉文件等等操作,都可以放在 exit 方法中完成。另外,exit 還可以進行異常的監(jiān)控和處理。
注意,如果要跳過一個異常,只需要__exit__返回True即可。如果返回False,將拋出異常。
# 下面示例將跳過所有的TypeError,而讓其他異常正常拋出。
class Test:
name = "上下文管理器"
def __enter__(self):
return self
def __exit__(self, type, value, trace):
return isinstance(value, TypeError)
with Test() as t:
print(t.name)
raise TypeError()
總之,with-as表達式極大的簡化了每次寫finally的工作,這對保持代碼的優(yōu)雅性是有極大幫助的。如果有多個項,還可以如下面方式進行編寫。
with open("test1.txt", "w") as f1, open('test2.txt', "w") as f2:
f1.write("test1")
f2.write("test2")
因此,Python的with語句提供了一個有效的機制,讓代碼更簡練,同時在異常產(chǎn)生時,清理工作更簡單。
當(dāng)異常發(fā)?時,with語句會采取哪些步驟。
-
它把異常的type,value和traceback傳遞給__exit__?法。
-
它讓__exit__?法來處理異常。
-
如果__exit__返回的是True,那么這個異常就被優(yōu)雅地處理了。
-
如果__exit__返回的是True以外的任何東西,那么這個異常將被with語句拋出。
05 應(yīng)用
自定義打開文件
class myOpen(object):
def __init__(self,filename):
self.handle=open(filename)
print( "Resource:%s"%filename )
def __enter__(self):
print( "[enter %s]: 分配資源."%self.handle )
return self.handle # 可以返回不同的對象
def __exit__(self,exc_type,exc_value,exc_trackback):
print( "[exit %s]: 釋放資源." %self.handle )
if exc_trackback is None:
print( "[exit %s]: 無異常退出."%self.handle )
self.handle.close()
else:
print( "[exit %s]: 包含異常的退出."%self.handle )
return False # 可以省略,缺省的None也是被看做是False
with myOpen('test1.txt') as fp:
for line in fp.readlines():
print( line , " " )
'''
輸出結(jié)果:
Resource:test1.txt
[enter <_io.TextIOWrapper name='test1.txt' mode='r' encoding='cp936'>]: 分配資源.
1
2
[exit <_io.TextIOWrapper name='test1.txt' mode='r' encoding='cp936'>]: 釋放資源.
[exit <_io.TextIOWrapper name='test1.txt' mode='r' encoding='cp936'>]: 無異常退出.
'''
myOpen中的__enter__() 返回的是自身的引用,這個引用可以賦值給 as 子句中的fp變量; 返回值的類型可以根據(jù)實際需要設(shè)置為不同的類型,不必是上下文管理器對象本身。
exit() 方法中對變量exc_trackback進行檢測,如果不為 None,表示發(fā)生了異常,返回 False 表示需要由外部代碼邏輯對異常進行處理; 如果沒有發(fā)生異常,缺省的返回值為 None,在布爾環(huán)境中也是被看做 False,但是由于沒有異常發(fā)生,exit() 的三個參數(shù)都為 None,上下文管理代碼可以檢測這種情況,做正常處理。exit()方法的3個參數(shù),分別代表異常的類型、值、以及堆棧信息。
自定義上下文類
class Mycontex(object):
def __init__(self,name):
self.name=name
def __enter__(self):
print("進入enter")
return self
def do_self(self):
print(self.name)
def __exit__(self,exc_type,exc_value,traceback):
print("退出exit")
print(exc_type,exc_value)
with Mycontex('test') as mc:
mc.do_self()
'''
輸出結(jié)果:
進入enter
test
退出exit
None None
'''
自定義類必須包含上述幾個方法才能正確使用with關(guān)鍵字。即使程序發(fā)生了錯誤,Python解釋器終止了程序,但是我們的類 還是順利關(guān)閉了。
06 基于?成器的實現(xiàn)
還可以?裝飾器(decorators)和?成器(generators)來實現(xiàn)上下?管理器。
Python有個contextlib模塊專門?于這個?的。我們可以使??個?成器函數(shù)來實現(xiàn)? 個上下?管理器,?不是使??個類。
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name, 'w')
yield f
f.close()
這個實現(xiàn)?式看起來更加直觀和簡單。然?,這個?法需要關(guān)于?成器、yield 和裝飾器的?些知識。在這個例?中我們還沒有捕捉可能產(chǎn)?的任何異常。它的?作?式 和之前的?法?致相同。
簡單剖析下這個?法。
-
Python解釋器遇到了yield關(guān)鍵字。因為這個緣故它創(chuàng)建了?個?成器?不是?個 普通的函數(shù)。
-
因為這個裝飾器,contextmanager會被調(diào)?并傳?函數(shù)名(open_file)作為 參數(shù)。
-
contextmanager函數(shù)返回?個以GeneratorContextManager對象封裝過的 ?成器。
-
這個GeneratorContextManager被賦值給open_file函數(shù),我們實際上是在 調(diào)?GeneratorContextManager對象。文章來源:http://www.zghlxwxcb.cn/news/detail-658864.html
那現(xiàn)在我們既然知道了所有這些,我們可以?這個新?成的上下?管理器了,像這樣:文章來源地址http://www.zghlxwxcb.cn/news/detail-658864.html
with open_file('some_file') as f:
f.write('hola!')
到了這里,關(guān)于Python函數(shù)-上下文管理器的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!