python中with語(yǔ)句用起來(lái)很方便,那這后邊的原理就是python中的上下文管理器。
1.什么是上下文管理器
上下文管理器用來(lái)定義/控制代碼塊得執(zhí)行前得準(zhǔn)備工作,以及程序執(zhí)行后得收尾工作。比如文件讀寫前的打開(kāi),與讀寫后的關(guān)閉。 網(wǎng)絡(luò)請(qǐng)求的建立與斷開(kāi)等等。
下邊拿requests.session() 為例:
import requests
with requests.session() as session:
response_html = session.get('http://www.baidu.com').text
print (response_html)
為什么with語(yǔ)句塊能控制網(wǎng)絡(luò)的建立與釋放,原因是實(shí)現(xiàn)了上下文管理協(xié)議(context management protocol),相關(guān)的兩個(gè)魔法方法:
__enter__(self): 不接收參數(shù),定義上下文管理器在with語(yǔ)句塊開(kāi)頭所執(zhí)行的操作,其方法返回的值綁定到,/as 后邊所定義的變量。
__exit__(self, exc_type, exc_value, traceback):接收三個(gè)參數(shù),exc_type 表示錯(cuò)誤類型;exc_value 異常實(shí)例。有時(shí)會(huì)有參數(shù)傳給異常構(gòu)造方法,例如錯(cuò)誤消息,這些參數(shù)可以使用exception_type獲取。traceback:traceback對(duì)象。
session的上下文協(xié)議源碼

2.創(chuàng)建上下文管理器的兩種方式
我們重新實(shí)現(xiàn)一個(gè)類似open()的類,只是在創(chuàng)建文件對(duì)象時(shí)和關(guān)閉文件對(duì)象時(shí)增加了提示。
2.1 實(shí)現(xiàn)上下文協(xié)議
class File(object):
def __init__(self, file_path, mode):
self.file_path = file_path
self.mode = mode
def __enter__(self):
self.f = open(self.file_path, self.mode, encoding='utf-8')
print(f"{self.file_path} is open")
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
print(f"{self.file_path} is closed")
with File('test.txt', 'w') as f:
f.write('董小賤')
輸出結(jié)果:
test.txt is open
test.txt is closed
2.2 裝飾器實(shí)現(xiàn)
from contextlib import contextmanager
@contextmanager
def file(file_path, mode):
f = open(file_path, mode, encoding='utf-8')
print(f"{file_path} is open")
yield f
f.close()
print(f"{file_path} is closed")
with file('test.txt, 'w') as f:
f.write('董小賤')
輸出結(jié)果:
test.txt is open
test.txt is closed
在使用 @contextmanager 裝飾的生成器中,yield 語(yǔ)句的作用是把函數(shù)的定義體分成兩部 分:yield 語(yǔ)句前面的所有代碼在 with 塊開(kāi)始時(shí)(即解釋器調(diào)用 __enter__方法時(shí))執(zhí)行, yield 語(yǔ)句后面的代碼在 with 塊結(jié)束時(shí)(即調(diào)用 __exit__ 方法時(shí))執(zhí)行。
其實(shí)這里有個(gè)問(wèn)題,如果在with塊中拋出了一個(gè)異常,Python 解釋器會(huì)將其捕獲, 然后在 file 函數(shù)的 yield 表達(dá)式里再次拋出,但是代碼中沒(méi)有處理異常的代碼,所以函數(shù)會(huì)終止,導(dǎo)致文件不能被關(guān)閉。所以我們要在yield處處理異常。
所以第二版:
from contextlib import contextmanager
@contextmanager
def file(file_path, mode):
f = open(file_path, mode, encoding='utf-8')
print(f"{file_path} is open")
try:
yield f
except Exception as e:
print (e) # 處理異常的操作
finally:
f.close()
print(f"{file_path} is closed")
with file('24.txt', 'w') as f:
f.write('董小賤')
注:實(shí)現(xiàn)上下文管理協(xié)議,即可使用with塊。需要注意的是在 @contextmanager 裝飾器裝飾的生成器中,yield 與迭代沒(méi)有任何關(guān)系。