詳解利用上下文管理器擴(kuò)展Python計(jì)時(shí)器
上文中,我們一起學(xué)習(xí)了手把手教你實(shí)現(xiàn)一個(gè) Python 計(jì)時(shí)器。本文中,云朵君將和大家一起了解什么是上下文管理器 和 Python 的 with 語句,以及如何完成自定義。然后擴(kuò)展 Timer
以便它也可以用作上下文管理器。最后,使用 Timer 作為上下文管理器如何簡化我們自己的代碼。
上文中我們創(chuàng)建的第一個(gè) Python 計(jì)時(shí)器類,然后逐步擴(kuò)展我們 Timer
類,其代碼也是較為豐富強(qiáng)大。我們不能滿足于此,仍然需要模板一些代碼來使用Timer
:
- 首先,實(shí)例化類
- 其次,在要計(jì)時(shí)的代碼塊之前調(diào)用
.start()
- 最后,在代碼塊之后調(diào)用
.stop()
一個(gè) Python 定時(shí)器上下文管理器
Python 有一個(gè)獨(dú)特的構(gòu)造,用于在代碼塊之前和之后調(diào)用函數(shù):上下文管理器。
了解 Python 中的上下文管理器
上下文管理器長期以來一直是 Python 中重要的一部分。由 PEP 343 于 2005 年引入,并首次在 Python 2.5 中實(shí)現(xiàn)。可以使用 with
關(guān)鍵字識(shí)別代碼中的上下文管理器:
with?EXPRESSION?as?VARIABLE: ????BLOCK
EXPRESSION
是一些返回上下文管理器的 Python 表達(dá)式。首先上下文管理器綁定到變量名 VARIABLE
上,BLOCK
可以是任何常規(guī)的 Python 代碼塊。上下文管理器保證程序在 BLOCK
之前調(diào)用一些代碼,在 BLOCK
執(zhí)行之后調(diào)用一些其他代碼。這樣即使 BLOCK
引發(fā)異常,后者也是會(huì)照樣執(zhí)行。
上下文管理器最常見的用途是處理不同的資源,如文件、鎖和數(shù)據(jù)庫連接等。上下文管理器用于使用資源后釋放和清理資源。以下示例僅通過打印包含冒號(hào)的行來演示 timer.py
的基本結(jié)構(gòu)。此外,它展示了在 Python 中打開文件的常用習(xí)語:
with?open("timer.py")?as?fp: ????print("".join(ln?for?ln?in?fp?if?":"?in?ln)) class?TimerError(Exception): class?Timer: ????timers:?ClassVar[Dict[str,?float]]?=?{} ????name:?Optional[str]?=?None ????text:?str?=?"Elapsed?time:?{:0.4f}?seconds" ????logger:?Optional[Callable[[str],?None]]?=?print ????_start_time:?Optional[float]?=?field(default=None,?init=False,?repr=False) ????def?__post_init__(self)?->?None: ????????if?self.name?is?not?None: ????def?start(self)?->?None: ????????if?self._start_time?is?not?None: ????def?stop(self)?->?float: ????????if?self._start_time?is?None: ????????if?self.logger: ????????if?self.name:
注意,使用 open()
作為上下文管理器,文件指針fp
不會(huì)顯式關(guān)閉,可以確認(rèn) fp
已自動(dòng)關(guān)閉:
fp.closed
True
在此示例中,open("timer.py")
是一個(gè)返回上下文管理器的表達(dá)式。該上下文管理器綁定到名稱 fp
。上下文管理器在 print()
執(zhí)行期間有效。這個(gè)單行代碼塊在 fp
的上下文中執(zhí)行。
fp
是上下文管理器是什么意思? 從技術(shù)上講,就是 fp
實(shí)現(xiàn)了 上下文管理器協(xié)議。Python 語言底層有許多不同的協(xié)議??梢詫f(xié)議視為說明我們代碼必須實(shí)現(xiàn)哪些特定方法的合同。
上下文管理器協(xié)議由兩種方法組成:
- 進(jìn)入與上下文管理器相關(guān)的上下文時(shí)調(diào)用
.__enter__()
。 - 退出與上下文管理器相關(guān)的上下文時(shí)調(diào)用
.__exit__()
。
換句話說,要自己創(chuàng)建上下文管理器,需要編寫一個(gè)實(shí)現(xiàn) .__enter__()
和 .__exit__()
的類。試試 Hello, World!
上下文管理器示例:
#?studio.py class?Studio: ????def?__init__(self,?name): ????????self.name?=?name ????def?__enter__(self): ????????print(f"你好?{self.name}") ????????return?self ????def?__exit__(self,?exc_type,?exc_value,?exc_tb): ????????print(f"一會(huì)兒見,?{self.name}")
Studio
是一個(gè)上下文管理器,它實(shí)現(xiàn)了上下文管理器協(xié)議,使用如下:
from?studio?import?Studio with?Studio("云朵君"): ????print("正在忙?...")
你好 云朵君
正在忙 ...
一會(huì)兒見, 云朵君
首先,注意 .__enter__()
在做事之前是如何被調(diào)用的,而 .__exit__()
是在做事之后被調(diào)用的。該示例中,沒有引用上下文管理器,因此不需要使用 as
為上下文管理器命名。
接下來,注意 self.__enter__()
的返回值受 as
約束。創(chuàng)建上下文管理器時(shí),通常希望從 .__enter__()
返回 self
??梢园慈缦路绞绞褂迷摲祷刂担?/p>
from?greeter?import?Greeter with?Greeter("云朵君")?as?grt: ??print(f"{grt.name}?正在忙?...")
你好 云朵君
云朵君 正在忙 ...
一會(huì)兒見, 云朵君
在寫 __exit__
函數(shù)時(shí),需要注意的事,它必須要有這三個(gè)參數(shù):
exc_type
:異常類型exc_val
:異常值exc_tb
:異常的錯(cuò)誤棧信息
這三個(gè)參數(shù)用于上下文管理器中的錯(cuò)誤處理,它們以 sys.exc_info()
的返回值返回。當(dāng)主邏輯代碼沒有報(bào)異常時(shí),這三個(gè)參數(shù)將都為None。
如果在執(zhí)行塊時(shí)發(fā)生異常,那么代碼將使用異常類型、異常實(shí)例和回溯對(duì)象(即exc_type
、exc_value
和exc_tb
)調(diào)用 .__exit__()
。通常情況下,這些在上下文管理器中會(huì)被忽略,而在引發(fā)異常之前調(diào)用 .__exit__()
:
from?greeter?import?Greeter with?Greeter("云朵君")?as?grt: ????print(f"{grt.age}?does?not?exist")
你好 云朵君
一會(huì)兒見, 云朵君
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'Greeter' object has no attribute 'age'
可以看到,即使代碼中有錯(cuò)誤,還是照樣打印了 "一會(huì)兒見, 云朵君"
。
理解并使用 contextlib
現(xiàn)在我們初步了解了上下文管理器是什么以及如何創(chuàng)建自己的上下文管理器。在上面的例子中,我們只是為了構(gòu)建一個(gè)上下文管理器,卻寫了一個(gè)類。如果只是要實(shí)現(xiàn)一個(gè)簡單的功能,寫一個(gè)類未免有點(diǎn)過于繁雜。這時(shí)候,我們就想,如果只寫一個(gè)函數(shù)就可以實(shí)現(xiàn)上下文管理器就好了。
這個(gè)點(diǎn)Python早就想到了。它給我們提供了一個(gè)裝飾器,你只要按照它的代碼協(xié)議來實(shí)現(xiàn)函數(shù)內(nèi)容,就可以將這個(gè)函數(shù)對(duì)象變成一個(gè)上下文管理器。
我們按照 contextlib 的協(xié)議來自己實(shí)現(xiàn)一個(gè)上下文管理器,為了更加直觀我們換個(gè)用例,創(chuàng)建一個(gè)我們常用且熟悉的打開文件(with open)的上下文管理器。
import?contextlib @contextlib.contextmanager def?open_func(file_name): ????#?__enter__方法 ????print('open?file:',?file_name,?'in?__enter__') ????file_handler?=?open(file_name,?'r') ? ????#?【重點(diǎn)】:yield ????yield?file_handler ????#?__exit__方法 ????print('close?file:',?file_name,?'in?__exit__') ????file_handler.close() ????return with?open_func('test.txt')?as?file_in: ????for?line?in?file_in: ????????print(line)
在被裝飾函數(shù)里,必須是一個(gè)生成器(帶有yield
),而 yield
之前的代碼,就相當(dāng)于__enter__
里的內(nèi)容。yield
之后的代碼,就相當(dāng)于__exit__
里的內(nèi)容。
上面這段代碼只能實(shí)現(xiàn)上下文管理器的第一個(gè)目的(管理資源),并不能實(shí)現(xiàn)第二個(gè)目的(處理異常)。
如果要處理異常,可以改成下面這個(gè)樣子。
import?contextlib @contextlib.contextmanager def?open_func(file_name): ????#?__enter__方法 ????print('open?file:',?file_name,?'in?__enter__') ????file_handler?=?open(file_name,?'r') ????try: ????????yield?file_handler ????except?Exception?as?exc: ????????#?deal?with?exception ????????print('the?exception?was?thrown') ????finally: ????????print('close?file:',?file_name,?'in?__exit__') ????????file_handler.close() ????????return with?open_func('test.txt')?as?file_in: ????for?line?in?file_in: ????????1/0 ????????print(line)
Python 標(biāo)準(zhǔn)庫中的 contextlib
包括定義新上下文管理器的便捷方法,以及可用于關(guān)閉對(duì)象、抑制錯(cuò)誤甚至什么都不做的現(xiàn)成上下文管理器!
創(chuàng)建 Python 計(jì)時(shí)器上下文管理器
了解了上下文管理器的一般工作方式后,要想知道它們是如何幫助處理時(shí)序代碼呢?假設(shè)如果可以在代碼塊之前和之后運(yùn)行某些函數(shù),那么就可以簡化 Python 計(jì)時(shí)器的工作方式。其實(shí),上下文管理器可以自動(dòng)為計(jì)時(shí)時(shí)顯式調(diào)用 .start()
和.stop()
。
同樣,要讓 Timer 作為上下文管理器工作,它需要遵守上下文管理器協(xié)議,換句話說,它必須實(shí)現(xiàn) .__enter__()
和 .__exit__()
方法來啟動(dòng)和停止 Python 計(jì)時(shí)器。從目前的代碼中可以看出,所有必要的功能其實(shí)都已經(jīng)可用,因此只需將以下方法添加到之前編寫的的 Timer
類中即可:
#?timer.py @dataclass class?Timer: ????#?其他代碼保持不變 ????def?__enter__(self): ????????"""Start?a?new?timer?as?a?context?manager""" ????????self.start() ????????return?self ????def?__exit__(self,?*exc_info): ????????"""Stop?the?context?manager?timer""" ????????self.stop()
Timer 現(xiàn)在就是一個(gè)上下文管理器。實(shí)現(xiàn)的重要部分是在進(jìn)入上下文時(shí), .__enter__()
調(diào)用 .start()
啟動(dòng) Python 計(jì)時(shí)器,而在代碼離開上下文時(shí), .__exit__()
使用 .stop()
停止 Python 計(jì)時(shí)器。
from?timer?import?Timer import?time with?Timer(): ????time.sleep(0.7)
Elapsed time: 0.7012 seconds
此處注意兩個(gè)更微妙的細(xì)節(jié):
.__enter__()
返回self
,Timer 實(shí)例,它允許用戶使用as
將 Timer 實(shí)例綁定到變量。例如,使用with Timer() as t:
將創(chuàng)建指向 Timer 對(duì)象的變量t
。.__exit__()
需要三個(gè)參數(shù),其中包含有關(guān)上下文執(zhí)行期間發(fā)生的任何異常的信息。代碼中,這些參數(shù)被打包到一個(gè)名為exc_info
的元組中,然后被忽略,此時(shí) Timer 不會(huì)嘗試任何異常處理。
在這種情況下不會(huì)處理任何異常。上下文管理器的一大特點(diǎn)是,無論上下文如何退出,都會(huì)確保調(diào)用.__exit__()
。在以下示例中,創(chuàng)建除零公式模擬異常查看代碼功能:
from?timer?import?Timer with?Timer(): ????for?num?in?range(-3,?3): ????????print(f"1?/?{num}?=?{1?/?num:.3f}")
1 / -3 = -0.333
1 / -2 = -0.500
1 / -1 = -1.000
Elapsed time: 0.0001 seconds
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
ZeroDivisionError: division by zero
注意 ,即使代碼拋出異常,Timer 也會(huì)打印出經(jīng)過的時(shí)間。
使用 Python 定時(shí)器上下文管理器
現(xiàn)在我們將一起學(xué)習(xí)如何使用 Timer 上下文管理器來計(jì)時(shí) "下載數(shù)據(jù)" 程序?;叵胍幌轮笆侨绾问褂?Timer 的:
#?download_data.py import?requests from?timer?import?Timer def?main(): ????t?=?Timer() ????t.start() ????source_url?=?'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1' ????headers?=?{'User-Agent':?'Mozilla/5.0'} ????res?=?requests.get(source_url,?headers=headers)? ????t.stop() ????with?open('dataset/datasets.zip',?'wb')?as?f: ????????f.write(res.content) if?__name__?==?"__main__": ????main()
我們正在對(duì) requests.get()
的調(diào)用進(jìn)行記時(shí)監(jiān)控。使用上下文管理器可以使代碼更短、更簡單、更易讀:
#?download_data.py import?requests from?timer?import?Timer def?main(): ????source_url?=?'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1' ????headers?=?{'User-Agent':?'Mozilla/5.0'} ????with?Timer(): ????????res?=?requests.get(source_url,?headers=headers) ???????? ????with?open('dataset/datasets.zip',?'wb')?as?f: ????????f.write(res.content) if?__name__?==?"__main__": ????main()
此代碼實(shí)際上與上面的代碼相同。主要區(qū)別在于沒有定義無關(guān)變量t
,在命名空間上無多余的東西。
寫在最后
將上下文管理器功能添加到 Python 計(jì)時(shí)器類有幾個(gè)優(yōu)點(diǎn):
- 省時(shí)省力:只需要一行額外的代碼即可為代碼塊的執(zhí)行計(jì)時(shí)。
- 可讀性高:調(diào)用上下文管理器是可讀的,你可以更清楚地可視化你正在計(jì)時(shí)的代碼塊。
使用 Timer
作為上下文管理器幾乎與直接使用 .start()
和 .stop()
一樣靈活,同時(shí)它的樣板代碼更少。
以上就是詳解利用上下文管理器擴(kuò)展Python計(jì)時(shí)器的詳細(xì)內(nèi)容,更多關(guān)于Python上下文管理器 計(jì)時(shí)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python實(shí)現(xiàn)提取COCO,VOC數(shù)據(jù)集中特定的類
這篇文章主要介紹了python實(shí)現(xiàn)提取COCO,VOC數(shù)據(jù)集中特定的類,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03實(shí)現(xiàn)用python算法計(jì)算圓周率的小訣竅
什么!你不會(huì)背圓周率(鄙夷的眼神) 3.1415926535 8979323846 26433... 但是,我會(huì)算啊,本文用一個(gè)簡單的python代碼,教你計(jì)算圓周率2021-08-08詳解用Python練習(xí)畫個(gè)美隊(duì)盾牌
這篇文章主要介紹了用Python練習(xí)畫個(gè)美隊(duì)盾牌,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03python實(shí)現(xiàn)flappy bird游戲
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)flappy bird游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12Python基于動(dòng)態(tài)規(guī)劃算法解決01背包問題實(shí)例
這篇文章主要介紹了Python基于動(dòng)態(tài)規(guī)劃算法解決01背包問題,結(jié)合實(shí)例形式分析了Python動(dòng)態(tài)規(guī)劃算法解決01背包問題的原理與具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12django之用戶、用戶組及權(quán)限設(shè)置方式
這篇文章主要介紹了django之用戶、用戶組及權(quán)限設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05pytorch實(shí)現(xiàn)手寫數(shù)字圖片識(shí)別
這篇文章主要為大家詳細(xì)介紹了pytorch實(shí)現(xiàn)手寫數(shù)字圖片識(shí)別,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05