什么是上下文管理器
上下文管理器是一個對象屠橄,它定義了在執(zhí)行 with 語句時要建立的運行時上下文。 上下文管理器處理進入和退出所需運行時上下文以執(zhí)行代碼塊辜羊。 通常使用 with 語句(在 with 語句中描述)蝗肪,但是也可以通過直接調(diào)用它們的方法來使用。
首先我們看下面操作文件的代碼贰谣,理清幾個概念娜搂,不要弄混了
with open("test.txt") as f:
print(f.readlines())
-
with open("test.txt") as f
:上下文表達式 -
open("test.txt")
:上下文管理器 -
f
:至于f,f不是上下文管理器吱抚,f應(yīng)該是資源對象
上下文管理協(xié)議
- 在一個類中百宇,如果實現(xiàn)了
__enter__
和__exit__
這兩個魔法方法,這個類的實例就是一個上下文管理器秘豹。 - 如果使用了上下文管理器携御,盡管with沒有調(diào)用魔法方法,但是with在代碼塊執(zhí)行前還是會先執(zhí)行
__enter__
既绕,在代碼執(zhí)行結(jié)束或出錯的時候執(zhí)行__enter__
-
__enter__
: with語句中的代碼塊執(zhí)行前執(zhí)行__enter__
, 返回的值將賦值給with句中as后的變量. -
__exit__
: with語句中的代碼塊執(zhí)行結(jié)束或出錯, 會執(zhí)行__exit__
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
def operate(self):
print('===in operation===')
with Resource() as res:
res.operate()
"""
輸出
===connect to resource===
===in operation===
===close resource connection===
"""
- 在編寫代碼時啄刹,我們一般將資源的連接或者獲取放在
__enter__
中,而將資源的關(guān)閉寫在__exit__
中凄贩。
為什么要使用上下文管理器
- 使用上下文管理器會讓代碼看起來更簡潔優(yōu)雅誓军,這也是Python一直追求的。我們可以用上下文管理器操作(創(chuàng)建/獲取/釋放)資源怎炊,如文件操作谭企、數(shù)據(jù)庫連接;
- 也可以用上下文管理器處理異常评肆。我們一般用try...except...來處理異常但是這樣做一個不好的地方是债查,在代碼的主邏輯里,會有大量的異常處理代理瓜挽,這會很大的影響我們的可讀性盹廷。如果用上下文管理器,就可以使用with將異常的處理隱藏起來久橙。(也就是說俄占,with大大簡化了try...except..語句的異常處理)
舉個栗子,下面的代碼我們將操作1/0
這個錯誤淆衷「组看看是否不報錯。
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
print(exc_type, exc_val, exc_tb)
return True
def operate(self):
1/0 # 分母不能為0祝拯,這里應(yīng)該報錯
with Resource() as res:
res.operate()
"""
輸出
===connect to resource===
===close resource connection===
<class 'ZeroDivisionError'> division by zero <traceback object at 0x0000024A3C452E08>
"""
這就是上下文管理協(xié)議的一個強大之處甚带,異乘希可以在__exit__
進行捕獲并由你自己決定如何處理,是拋出呢還是在這里就解決了鹰贵。在__exit__
里返回 True
(沒有return 就默認為 return False)晴氨,就相當于告訴 Python解釋器,這個異常我們已經(jīng)捕獲了碉输,不需要再往外拋了籽前。
在 寫__exit__
函數(shù)時,需要注意的事敷钾,它必須要有這三個參數(shù):
- exc_type:異常類型
- exc_val:異常值
- exc_tb:異常的錯誤棧信息
當主邏輯代碼沒有報異常時枝哄,這三個參數(shù)將都為None。
理解并使用裝飾器 contextlib
上面說了闰非,如果要定義上下文管理器膘格,就需要在類中定義
__enter__
和__exit__
。在Python中也提供了一個@contextlib
裝飾器财松,可以省略兩個魔法方法。該裝飾器位于contextlib模塊下
from contextlib import contextmanager
- 我們借助contextmanager裝飾器纱控,可以不使用兩個魔法方法辆毡。但是這里注意我們只是不需要定義
__enter__
和__exit__
這兩個方法,但是他們里面所執(zhí)行的語句我們還是需要實現(xiàn)的甜害。在進入上下文管理器的時候打印__enter__
里面的方法舶掖,在退出的時候打印__exit__
里面的方法。
from contextlib import contextmanager
@contextmanager
def open_func(file_name):
# __enter__ 方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
# 【重點】:yield 返回的內(nèi)容復(fù)制給as之后的變量
yield file_handler
# __exit__方法
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('E:/hello.txt') as f:
for line in f:
print(line)
"""
輸出
open file: E:/hello.txt in __enter__
1
2
3
close file: E:/hello.txt in __exit__
"""
上面代碼的執(zhí)行過程:
- with語句中的代碼塊執(zhí)行函數(shù)中yield語句之前的代碼尔店,相當于執(zhí)行
__enter__
方法眨攘。 - yield返回的內(nèi)容復(fù)制給as之后的變量,也就是f嚣州。 在被裝飾函數(shù)里鲫售,必須是一個生成器(帶有yield)
- with語句中的代碼塊執(zhí)行函數(shù)中yield語句之后的代碼,相當于執(zhí)行
__exit__
方法该肴。
上下文管理器的三個好處
- 提高代碼的復(fù)用率
- 提高代碼的優(yōu)雅度
- 提高代碼的可讀性