<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
上文中,我們一起學習了手把手教你實現一個 Python 計時器。本文中,雲朵君將和大家一起了解什麼是上下文管理器 和 Python 的 with 語句,以及如何完成自定義。然後擴充套件 Timer
以便它也可以用作上下文管理器。最後,使用 Timer 作為上下文管理器如何簡化我們自己的程式碼。
上文中我們建立的第一個 Python 計時器類,然後逐步擴充套件我們 Timer
類,其程式碼也是較為豐富強大。我們不能滿足於此,仍然需要模板一些程式碼來使用Timer
:
.start()
.stop()
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
:異常的錯誤棧資訊這三個引數用於上下文管理器中的錯誤處理,它們以 sys.exc_info()
的返回值返回。當主邏輯程式碼沒有報異常時,這三個引數將都為None。
如果在執行塊時發生異常,那麼程式碼將使用異常型別、異常範例和回溯物件(即exc_type
、exc_value
和exc_tb
)呼叫 .__exit__()
。通常情況下,這些在上下文管理器中會被忽略,而在引發異常之前呼叫 .__exit__()
:
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'
可以看到,即使程式碼中有錯誤,還是照樣列印了 "一會兒見, 雲朵君"
。
現在我們初步瞭解了上下文管理器是什麼以及如何建立自己的上下文管理器。在上面的例子中,我們只是為了構建一個上下文管理器,卻寫了一個類。如果只是要實現一個簡單的功能,寫一個類未免有點過於繁雜。這時候,我們就想,如果只寫一個函數就可以實現上下文管理器就好了。
這個點Python早就想到了。它給我們提供了一個裝飾器,你只要按照它的程式碼協定來實現函數內容,就可以將這個函數物件變成一個上下文管理器。
我們按照 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 計時器的工作方式。其實,上下文管理器可以自動為計時時顯式呼叫 .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 也會列印出經過的時間。
現在我們將一起學習如何使用 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()
一樣靈活,同時它的樣板程式碼更少。
以上就是詳解利用上下文管理器擴充套件Python計時器的詳細內容,更多關於Python上下文管理器 計時器的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45