contextmanager
一.contextmanager修飾器的解釋
[email protected]
此函數是一個裝飾器,可以不需要創建新的類或者獨立的__enter__() 和__exit__(),爲context managers (上下文管理器)來定義factory function(工廠函數)的方法。
雖然許多對象本身支持在with語句中使用,但有時候需要去管理資源本身,而不是上下文管理,並且不能和contextlib.closing一起應用close()的方法。
context managers可以精確的分配和釋放資源,比如我們常用的with:
with open('something.txt', 'w') as f:
f.write(sth)
被修飾的函數在被調用時必須返回generator-iterator(生成器迭代器)。這個迭代器必須只產生一個值,(如果有的話)這個值將綁定到with語句的as子句中的目標。如下例中的resource。
在generator(生成器)yield(生成)時,執行嵌套在with語句中的塊。然後在塊運行完畢,並退出後,generator恢復並繼續運行。如果塊中發生了未處理的異常,它將會在generator內部yield出現異常的點處重新生成。因此,可以嘗試try…except…finally語句來捕捉錯誤,或者採取一些清理措施。如果被捕捉的異常僅僅是爲了記錄或者執行某系操作,並非完全抑制程序進行,這個generator必須衝重新加載異常。否則,generator context manager將會向with語句指示已經處理的異常,並繼續執行跟在with語句後面的語句。
from contextlib import contextmanager
@contextmanager
def managed_resource(*args, **kwds):
# 獲取資源,可以從文本中讀取資源,read()等
resource = acquire_resource(*args, **kwds)
try:
yield resource
finally:
# 發佈資源
release_resource(resource)
>>> with managed_resource(timeout=3600) as resource:
... # 即使塊中的代碼引發了異常,這個塊中的資源也會在結尾發佈
2.For example
可以採取以下的形式,將時間函數裝飾起來,以便於算法優化時候,通過時間判斷其計算能力,十分方便。(以下只是代碼應用的方式)
@contextmanager
def timer(name):
start = time.time()
yield
end = time.time()
print('{} COST: {}'.format(name, end - start))
with timer('Timer Evaluation: '):
evalution(function)
二.例子解釋
1.無異常情況
正常情況下,當程序運行到yield時,調用with語句部分,然後繼續執行原語句。
from contextlib import contextmanager
@contextmanager
def example():
resource = ['a', 'b', 'c', 'd', 'e']
print('start')
try:
yield resource
finally:
print('end')
with example() as resource:
for i in resource:
print(i)
'''
start
a
b
c
d
e
end
'''
2.異常情況
當設定一個錯誤在with語句中,當contextmanager執行到yield時,發生錯誤,便跳出with,繼續執行後面的語句,輸出end。
當然將異常部分放到yield的前面,此時不會執行yield後面source的部分,只會輸出start 和end。
from contextlib import contextmanager
@contextmanager
def example():
resource = ['a', 'b', 'c', 'd', 'e']
print('start')
try:
# raise Exception('test')
yield resource
finally:
print('end')
with example() as resource:
for i in resource:
print(i)
if i == 'd':
raise Exception('test')
'''
start
a
b
c
d
end
Traceback (most recent call last):
File "example.py", line 18, in <module>
raise Exception('test')
Exception: test
'''
將resource從類中調用:
from contextlib import contextmanager
class Resource:
def query(self):
print('query data')
@contextmanager
def make_resource():
print('start to connect')
yield Resource()
print('end connect')
pass
with make_resource() as r:
r.query()
三.總結
yield的作用
yield前面的部分可以看作是一個代碼塊在執行操作,其後面的部分可以當做contextmanager中exit函數中。
Python的迭代協議:可迭代的對象定義了一個__next__方法,它要麼返回迭代中的下一項,或者引發一個特殊的StopIteration異常來終止迭代。
要支持這一協議,函數包含一條yield語句,該語句特別編譯爲生成器。當調用時,它們返回一個迭代器對象,該對象支持用一個名爲__next__的自動創建的方法來繼續執行的接口。生成器函數也可能有一條return語句,總是在def語句塊的末尾,直接終止值的生成。從技術上講,可以在任何常規函數退出執行之後,引發一個StopIteration異常來實現。從調用者的角度來看,生成器的__next__方法繼續函數並且運行到下一個yield結果返回或引發一個StopIteration異常。
直接效果就是生成器函數,編寫爲包含yield語句的def語句,自動支持迭代協議,並且由此可能用在任何迭代環境中以隨着時間並根據需要產生結果。
參考資料:
[1]:https://docs.python.org/3.6/
[2]:https://www.cnblogs.com/Clisa/p/6106162.html