如何用上下文管理器擴充 Python 計時器
上文中我們建立的第一個 Python 計時器類,然後逐步擴展我們 Timer 類,其程式碼也是較為豐富強大。我們不能滿足於此,仍然需要模板一些程式碼來使用Timer:
- 首先,實例化類別
- 其次,在要計時的程式碼區塊之前呼叫.start()
- 最後,在程式碼區塊之後呼叫.stop()
#一個Python 定時器上下文管理器
Python 有一個獨特的構造,用於在程式碼區塊之前和之後呼叫函數:上下文管理器。
了解 Python 中的上下文管理器
上下文管理器長期以來一直是 Python 中重要的一部分。由 PEP 343 於 2005 年引入,並首次在 Python 2.5 中實現。可以使用 with 關鍵字辨識程式碼中的上下文管理器:
with EXPRESSION as VARIABLE: BLOCK
EXPRESSION 是一些傳回上下文管理器的 Python 表達式。首先上下文管理器綁定到變數名稱 VARIABLE上,BLOCK 可以是任何常規的 Python 程式碼區塊。情境管理器保證程式在 BLOCK 之前呼叫一些程式碼,在 BLOCK 執行之後呼叫一些其他程式碼。這樣即使 BLOCK 引發異常,後者也是會照樣執行。
上下文管理器最常見的用途是處理不同的資源,如檔案、鎖定和資料庫連接等。上下文管理器用於使用資源後釋放和清理資源。以下範例僅透過列印包含冒號的行來示範 timer.py 的基本結構。此外,它展示了在Python 中開啟檔案的常用慣用語:
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 不會明確關閉,可以確認 fp 已自動關閉:
fp.closed
True
在此範例中,open("timer.py") 是一個傳回上下文管理器的運算式。此上下文管理器綁定到名稱 fp。上下文管理器在 print() 執行期間有效。這個單行程式碼區塊在 fp 的上下文中執行。
fp 是上下文管理器是什麼意思? 從技術上講,就是 fp 實現了 上下文管理器協定。 Python 語言底層有許多不同的協定。協議可以被視為說明我們程式碼必須實作哪些特定方法的合約。
上下文管理器協定由兩種方法組成:
- 進入與上下文管理器相關的上下文時呼叫.__enter__()。
- 退出與上下文管理器相關的上下文時呼叫.__exit__()。
換句話說,要自行建立情境管理器,需要寫一個實作 .__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"一会儿见, {self.name}")
Studio是一個上下文管理器,它實現了上下文管理器協議,使用如下:
from studio import Studio with Studio("云朵君"): print("正在忙 ...")
你好 云朵君 正在忙 ... 一会儿见, 云朵君
首先,注意 .__enter__()在做事之前是如何被召喚的,而 .__exit__() 是在做事之後被召喚的。在此範例中,沒有引用上下文管理器,因此不需要使用 as 為上下文管理器命名。
接下來,注意 self.__enter__() 的回傳值受 as 約束。建立情境管理器時,通常會希望從 .__enter__() 回傳 self 。可以如下使用此回傳值:
from greeter import Greeter with Greeter("云朵君") as grt: print(f"{grt.name} 正在忙 ...")
你好 云朵君 云朵君 正在忙 ... 一会儿见, 云朵君
在寫 __exit__ 函數時,需要注意的事,它必須要有這三個參數:
- ##exc_type:異常類型exc_val:異常值exc_tb:異常的錯誤堆疊資訊
from greeter import Greeter with Greeter("云朵君") as grt: print(f"{grt.age} does not exist")
你好 云朵君 一会儿见, 云朵君 Traceback (most recent call last): File "<stdin>", line 2, in <module> AttributeError: 'Greeter' object has no attribute 'age'
我们按照 contextlib 的协议来自己实现一个上下文管理器,为了更加直观我们换个用例,创建一个我们常用且熟悉的打开文件(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') # 【重点】: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)
在被装饰函数里,必须是一个生成器(带有yield),而 yield 之前的代码,就相当于__enter__里的内容。yield 之后的代码,就相当于__exit__ 里的内容。
上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。
如果要处理异常,可以改成下面这个样子。
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 标准库中的 contextlib包括定义新上下文管理器的便捷方法,以及可用于关闭对象、抑制错误甚至什么都不做的现成上下文管理器!
创建 Python 计时器上下文管理器
了解了上下文管理器的一般工作方式后,要想知道它们是如何帮助处理时序代码呢?假设如果可以在代码块之前和之后运行某些函数,那么就可以简化 Python 计时器的工作方式。其实,上下文管理器可以自动为计时时显式调用 .start() 和.stop()。
同样,要让 Timer 作为上下文管理器工作,它需要遵守上下文管理器协议,换句话说,它必须实现 .__enter__() 和 .__exit__() 方法来启动和停止 Python 计时器。从目前的代码中可以看出,所有必要的功能其实都已经可用,因此只需将以下方法添加到之前编写的的 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 现在就是一个上下文管理器。实现的重要部分是在进入上下文时, .__enter__() 调用 .start() 启动 Python 计时器,而在代码离开上下文时, .__exit__() 使用 .stop() 停止 Python 计时器。
from timer import Timer import time with Timer(): time.sleep(0.7)
Elapsed time: 0.7012 seconds
此处注意两个更微妙的细节:
- .__enter__() 返回self,Timer 实例,它允许用户使用as 将Timer 实例绑定到变量。例如,使用with Timer() as t: 将创建指向Timer 对象的变量t。
- .__exit__() 需要三个参数,其中包含有关上下文执行期间发生的任何异常的信息。代码中,这些参数被打包到一个名为exc_info 的元组中,然后被忽略,此时 Timer 不会尝试任何异常处理。
在这种情况下不会处理任何异常。上下文管理器的一大特点是,无论上下文如何退出,都会确保调用.__exit__()。在以下示例中,创建除零公式模拟异常查看代码功能:
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 也会打印出经过的时间。
使用 Python 定时器上下文管理器
现在我们将一起学习如何使用 Timer 上下文管理器来计时 "下载数据" 程序。回想一下之前是如何使用 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()
我们正在对 requests.get() 的调用进行记时监控。使用上下文管理器可以使代码更短、更简单、更易读:
# 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()
此代码实际上与上面的代码相同。主要区别在于没有定义无关变量t,在命名空间上无多余的东西。
写在最后
将上下文管理器功能添加到 Python 计时器类有几个优点:
- 省时省力:只需要一行额外的代码即可为代码块的执行计时。
- 可读性高:调用上下文管理器是可读的,你可以更清楚地可视化你正在计时的代码块。
使用 Timer 作为上下文管理器几乎与直接使用 .start() 和 .stop() 一样灵活,同时它的样板代码更少。在该系列下一篇文章中,云朵君将和大家一起学习如何将 Timer 也用作装饰器,并用于代码中,从而更加容易地监控代码完整运行过程,我们一起期待吧!
以上是如何用上下文管理器擴充 Python 計時器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。
