溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么用上下文管理器擴(kuò)展Python計(jì)時(shí)器

發(fā)布時(shí)間:2023-04-13 11:27:24 來(lái)源:億速云 閱讀:82 作者:iii 欄目:編程語(yǔ)言

本文小編為大家詳細(xì)介紹“怎么用上下文管理器擴(kuò)展Python計(jì)時(shí)器”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“怎么用上下文管理器擴(kuò)展Python計(jì)時(shí)器”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

一個(gè) Python 定時(shí)器上下文管理器

Python 有一個(gè)獨(dú)特的構(gòu)造,用于在代碼塊之前和之后調(diào)用函數(shù):上下文管理器。

了解 Python 中的上下文管理器

上下文管理器長(zhǎng)期以來(lái)一直是 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ù)庫(kù)連接等。上下文管理器用于使用資源后釋放和清理資源。以下示例僅通過(guò)打印包含冒號(hào)的行來(lái)演示 timer.py 的基本結(jié)構(gòu)。此外,它展示了在 Python 中打開文件的常用習(xí)語(yǔ):

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 語(yǔ)言底層有許多不同的協(xié)議??梢詫f(xié)議視為說(shuō)明我們代碼必須實(shí)現(xiàn)哪些特定方法的合同。

上下文管理器協(xié)議由兩種方法組成:

  1. 進(jìn)入與上下文管理器相關(guān)的上下文時(shí)調(diào)用.__enter__()。

  2. 退出與上下文管理器相關(guān)的上下文時(shí)調(diào)用.__exit__()。

換句話說(shuō),要自己創(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 為上下文管理器命名。

接下來(lái),注意 self.__enter__() 的返回值受 as 約束。創(chuàng)建上下文管理器時(shí),通常希望從 .__enter__() 返回 self 。可以按如下方式使用該返回值:

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 "", line 2, inAttributeError: 'Greeter' object has no attribute 'age'

可以看到,即使代碼中有錯(cuò)誤,還是照樣打印了 "一會(huì)兒見, 云朵君"。

理解并使用 contextlib

現(xiàn)在我們初步了解了上下文管理器是什么以及如何創(chuàng)建自己的上下文管理器。在上面的例子中,我們只是為了構(gòu)建一個(gè)上下文管理器,卻寫了一個(gè)類。如果只是要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的功能,寫一個(gè)類未免有點(diǎn)過(guò)于繁雜。這時(shí)候,我們就想,如果只寫一個(gè)函數(shù)就可以實(shí)現(xiàn)上下文管理器就好了。

這個(gè)點(diǎn)Python早就想到了。它給我們提供了一個(gè)裝飾器,你只要按照它的代碼協(xié)議來(lái)實(shí)現(xiàn)函數(shù)內(nèi)容,就可以將這個(gè)函數(shù)對(duì)象變成一個(gè)上下文管理器。

我們按照 contextlib 的協(xié)議來(lái)自己實(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)庫(kù)中的 contextlib包括定義新上下文管理器的便捷方法,以及可用于關(guān)閉對(duì)象、抑制錯(cuò)誤甚至什么都不做的現(xiàn)成上下文管理器!

創(chuàng)建 Python 計(jì)時(shí)器上下文管理器

了解了上下文管理器的一般工作方式后,要想知道它們是如何幫助處理時(shí)序代碼呢?假設(shè)如果可以在代碼塊之前和之后運(yùn)行某些函數(shù),那么就可以簡(jiǎn)化 Python 計(jì)時(shí)器的工作方式。其實(shí),上下文管理器可以自動(dòng)為計(jì)時(shí)時(shí)顯式調(diào)用 .start() 和.stop()。

同樣,要讓 Timer 作為上下文管理器工作,它需要遵守上下文管理器協(xié)議,換句話說(shuō),它必須實(shí)現(xiàn) .__enter__() 和 .__exit__() 方法來(lái)啟動(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)是,無(wú)論上下文如何退出,都會(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 "", line 3, inZeroDivisionError: division by zero

注意 ,即使代碼拋出異常,Timer 也會(huì)打印出經(jīng)過(guò)的時(shí)間。

使用 Python 定時(shí)器上下文管理器

現(xiàn)在我們將一起學(xué)習(xí)如何使用 Timer 上下文管理器來(lái)計(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)控。使用上下文管理器可以使代碼更短、更簡(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ū)別在于沒有定義無(wú)關(guān)變量t,在命名空間上無(wú)多余的東西。

讀到這里,這篇“怎么用上下文管理器擴(kuò)展Python計(jì)時(shí)器”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI