欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

手把手帶你用Python實現(xiàn)一個計時器

 更新時間:2022年06月28日 14:09:40   作者:云朵君  
雖然Python是一種有效的編程語言,但純Python程序比C、Rust和Java等編譯語言中的對應程序運行得更慢,為了更好地監(jiān)控和優(yōu)化Python程序,今天將為大家介紹如何使用?Python?計時器來監(jiān)控程序運行的速度,以便正對性改善代碼性能

雖然許多數(shù)據(jù)工作者認為 Python 是一種有效的編程語言,但純 Python 程序比C、Rust 和 Java 等編譯語言中的對應程序運行得更慢,為了更好地監(jiān)控和優(yōu)化Python程序,云朵君將和大家一起學習如何使用 Python 計時器來監(jiān)控程序運行的速度,以便正對性改善代碼性能。

為了更好地掌握 Python 計時器的應用,我們后面還補充了有關Python類、上下文管理器和裝飾器的背景知識。因篇幅限制,其中利用上下文管理器和裝飾器優(yōu)化 Python 計時器,將在后續(xù)文章學習,不在本篇文章范圍內。

Python 計時器

首先,我們向某段代碼中添加一個Python 計時器以監(jiān)控其性能。

Python 定時器函數(shù)

Python 中的內置time[1]模塊中有幾個可以測量時間的函數(shù):

  • monotonic()
  • perf_counter()
  • process_time()
  • time()

Python 3.7 引入了幾個新函數(shù),如thread_time()[2],以及上述所有函數(shù)的納秒版本,以_ns后綴命名。例如,perf_counter_ns()perf_counter()的納秒版本的。

perf_counter()返回性能計數(shù)器的值(以秒為單位),即具有最高可用分辨率的時鐘以測量短持續(xù)時間。

首先,使用perf_counter()創(chuàng)建一個 Python 計時器。將把它與其他 Python 計時器函數(shù)進行比較,看看 perf_counter() 的優(yōu)勢。

示例

創(chuàng)建一個腳本,定義一個簡短的函數(shù):從清華云上下載一組數(shù)據(jù)。

import?requests
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'}
????res?=?requests.get(source_url,?headers=headers)
????with?open('dataset/datasets.zip',?'wb')?as?f:
????????f.write(res.content)
???????
if?__name__=="__main__":
????main()

我們可以使用 Python 計時器來監(jiān)控該腳本的性能。

第一個 Python 計時器

現(xiàn)在使用函數(shù)time.perf_counter()函數(shù)創(chuàng)建一個計時器,這是一個非常適合針對部分代碼的性能計時的計數(shù)器。

perf_counter()從某個未指定的時刻開始測量時間(以秒為單位),這意味著對該函數(shù)的單個調用的返回值沒有用。但當查看對perf_counter()兩次調用之間的差異時,可以計算出兩次調用之間經(jīng)過了多少秒。

>>>?import?time
>>>?time.perf_counter()
394.540232282

>>>?time.perf_counter()??#?幾秒鐘后
413.31714087

在此示例中,兩次調用 perf_counter() 相隔近 19 秒??梢酝ㄟ^計算兩個輸出之間的差異來確認這一點:413.31714087 - 394.540232282 = 18.78

現(xiàn)在可以將 Python 計時器添加到示例代碼中:

#?download_data.py
import?requests
import?time
def?main():
????tic?=?time.perf_counter()
????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)
????with?open('dataset/datasets.zip',?'wb')?as?f:
????????f.write(res.content)
????toc?=?time.perf_counter()
????print(f"該程序耗時:?{toc?-?tic:0.4f}?seconds")
if?__name__=="__main__":
????main()

注意perf_counter()通過計算兩次調用之間的差異來打印整個程序運行所花費的時間。

print()函數(shù)中 f 字符串前面的表示這是一個 f-string ,這是格式化文本字符串的較為便捷的方式。:0.4f是一個格式說明符,表示數(shù)字,toc - tic應打印為帶有四位小數(shù)的十進制數(shù)。

運行程序可以看到程序經(jīng)過的時間:

該程序耗時: 0.026 seconds

就是這么簡單。接下來我們一起學習如何將 Python 計時器包裝到一個類、一個上下文管理器和一個裝飾器中,這樣可以更加一致和方便使用計時器。

一個 Python 定時器類

這里我們至少需要一個變量來存儲 Python 計時器的狀態(tài)。接下來我們創(chuàng)建一個與手動調用 perf_counter() 相同的類,但更具可讀性和一致性。

創(chuàng)建和更新Timer類,使用該類以多種不同方式對代碼進行計時。

$?python?-m?pip?install?codetiming

理解 Python 中的類

Class是面向對象編程的主要構建塊。類本質上是一個模板,可以使用它來創(chuàng)建對象。

在 Python 中,當需要對需要跟蹤特定狀態(tài)的事物進行建模時,類非常有用。一般來說,類是屬性的集合,稱為屬性,以及行為,稱為方法。

創(chuàng)建 Python 計時器類

類有利于跟蹤狀態(tài)。在Timer類中,想要跟蹤計時器何時開始以及已經(jīng)多少時間。對于Timer類的第一個實現(xiàn),將添加一個._start_time屬性以及.start().stop()方法。將以下代碼添加到名為 timer.py 的文件中:

#?timer.py
import?time
class?TimerError(Exception):
????"""一個自定義異常,用于報告使用Timer類時的錯誤"""

class?Timer:
????def?__init__(self):
????????self._start_time?=?None

????def?start(self):
????????"""Start?a?new?timer"""
????????if?self._start_time?is?not?None:
????????????raise?TimerError(f"Timer?is?running.?Use?.stop()?to?stop?it")
????????self._start_time?=?time.perf_counter()

????def?stop(self):
????????"""Stop?the?timer,?and?report?the?elapsed?time"""
????????if?self._start_time?is?None:
????????????raise?TimerError(f"Timer?is?not?running.?Use?.start()?to?start?it")

????????elapsed_time?=?time.perf_counter()?-?self._start_time
????????self._start_time?=?None
????????print(f"Elapsed?time:?{elapsed_time:0.4f}?seconds")

這里我們需要花點時間仔細地瀏覽代碼,會發(fā)現(xiàn)一些不同的事情。

首先定義了一個TimerError Python 類。該(Exception)符號表示TimerError 繼承自另一個名為Exception的父類。使用這個內置類進行錯誤處理。不需要向TimerError添加任何屬性或方法,但自定義錯誤可以更靈活地處理Timer內部問題。

接下來自定義Timer類。當從一個類創(chuàng)建或實例化一個對象時,代碼會調用特殊方法.__init__()初始化實例。在這里定義的第一個Timer版本中,只需初始化._start_time屬性,將用它來跟蹤 Python 計時器的狀態(tài),計時器未運行時它的值為None。計時器運行后,用它來跟蹤計時器的啟動時間。

注意: ._start_time的第一個下劃線(_)前綴是Python約定。它表示._start_time是Timer類的用戶不應該操作的內部屬性。

當調用.start()啟動新的 Python 計時器時,首先檢查計時器是否運行。然后將perf_counter()的當前值存儲在._start_time中。

另一方面,當調用.stop()時,首先檢查Python計時器是否正在運行。如果是,則將運行時間計算為perf_counter()的當前值與存儲在._start_time中的值的差值。最后,重置._start_time,以便重新啟動計時器,并打印運行時間。

以下是使用Timer方法:

from?timer?import?Timer
t?=?Timer()
t.start()
#?幾秒鐘后
t.stop()

Elapsed time: 3.8191 seconds

將此示例與前面直接使用perf_counter()的示例進行比較。代碼的結構相似,但現(xiàn)在代碼更清晰了,這也是使用類的好處之一。通過仔細選擇類、方法和屬性名稱,可以使你的代碼非常具有描述性!

使用 Python 計時器類

現(xiàn)在Timer類中寫入download_data.py。只需要對以前的代碼進行一些更改:

#?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)
????with?open('dataset/datasets.zip',?'wb')?as?f:
????????f.write(res.content)
????t.stop()
if?__name__=="__main__":
????main()

注意,該代碼與之前使用的代碼非常相似。除了使代碼更具可讀性之外,Timer還負責將經(jīng)過的時間打印到控制臺,使得所用時間的記錄更加一致。運行代碼時,得到的輸出幾乎相同:

Elapsed time: 0.502 seconds
...

打印經(jīng)過的時間Timer可能是一致的,但這種方法好像不是很靈活。下面我們添加一些更加靈活的東西到代碼中。

增加更多的便利性和靈活性

到目前為止,我們已經(jīng)了解到類適用于我們想要封裝狀態(tài)并確保代碼一致性的情況。在本節(jié)中,我們將一起給 Python 計時器加入更多便利性和靈活性,那怎么做呢?

  • 在報告消耗的時間時,使用可調整的文本和格式
  • 日志記錄打印到控制臺、寫入到日志文件或程序的其他部分
  • 創(chuàng)建一個可以在多次調用中可積累的Python計時器
  • 構建 Python 計時器的信息表示

首先,自定義用于報告所用時間的文本。在前面的代碼中,文本 f"Elapsed time: {elapsed_time:0.4f} seconds" 被生硬編碼到 .stop() 中。如若想使得類代碼更加靈活, 可以使用實例變量,其值通常作為參數(shù)傳遞給.__init__()并存儲到 self 屬性。為方便起見,我們還可以提供合理的默認值。

要添加.textTimer實例變量,可執(zhí)行以下操作timer.py

#?timer.py
def?__init__(self,?text="Elapsed?time:?{:0.4f}?seconds"):
????self._start_time?=?None
????self.text?=?text

注意,默認文本"Elapsed time: {:0.4f} seconds"是作為一個常規(guī)字符串給出的,而不是f-string。這里不能使用f-string,因為f-string會立即計算,當你實例化Timer時,你的代碼還沒有計算出消耗的時間。

注意: 如果要使用f-string來指定.text,則需要使用雙花括號來轉義實際經(jīng)過時間將替換的花括號。

如:f"Finished {task} in {{:0.4f}} seconds"。如果task的值是"reading",那么這個f-string將被計算為"Finished reading in {:0.4f} seconds"。

.stop()中,.text用作模板并使用.format()方法填充模板:

#?timer.py
def?stop(self):
????"""Stop?the?timer,?and?report?the?elapsed?time"""
????if?self._start_time?is?None:
????????raise?TimerError(f"Timer?is?not?running.?Use?.start()?to?start?it")

????elapsed_time?=?time.perf_counter()?-?self._start_time
????self._start_time?=?None
????print(self.text.format(elapsed_time))

在此更新為timer.py之后,可以將文本更改如下:

from?timer?import?Timer
t?=?Timer(text="You?waited?{:.1f}?seconds")
t.start()
#?幾秒鐘后
t.stop()

You waited 4.1 seconds

接下來,我們不只是想將消息打印到控制臺,還想保存時間測量結果,這樣可以便于將它們存儲在數(shù)據(jù)庫中??梢酝ㄟ^從.stop()返回elapsed_time的值來實現(xiàn)這一點。然后,調用代碼可以選擇忽略該返回值或保存它以供以后處理。

如果想要將Timer集成到日志logging中。要支持計時器的日志記錄或其他輸出,需要更改對print()的調用,以便用戶可以提供自己的日志記錄函數(shù)。這可以用類似于你之前定制的文本來完成:

#?timer.py
#?...
class?Timer:
????def?__init__(
????????self,
????????text="Elapsed?time:?{:0.4f}?seconds",
????????logger=print
????):
????????self._start_time?=?None
????????self.text?=?text
????????self.logger?=?logger
????#?其他方法保持不變
????def?stop(self):
????????"""Stop?the?timer,?and?report?the?elapsed?time"""
????????if?self._start_time?is?None:
????????????raise?TimerError(f"Timer?is?not?running.?Use?.start()?to?start?it")
????????elapsed_time?=?time.perf_counter()?-?self._start_time
????????self._start_time?=?None
????????if?self.logger:
????????????self.logger(self.text.format(elapsed_time))

????????return?elapsed_time

不是直接使用print(),而是創(chuàng)建另一個實例變量 self.logger,引用一個接受字符串作為參數(shù)的函數(shù)。除此之外,還可以對文件對象使用logging.info().write()等函數(shù)。還要注意if中,它允許通過傳遞logger=None來完全關閉打印。

以下是兩個示例,展示了新功能的實際應用:

from?timer?import?Timer
import?logging
t?=?Timer(logger=logging.warning)
t.start()
#?幾秒鐘后
t.stop()??#?A?few?seconds?later

WARNING:root:Elapsed time: 3.1610 seconds  
3.1609658249999484

t?=?Timer(logger=None)
t.start()
#?幾秒鐘后
value?=?t.stop()
value

4.710851433001153

接下來第三個改進是積累時間度量的能力。例如,在循環(huán)中調用一個慢速函數(shù)時,希望以命名計時器的形式添加更多的功能,并使用一個字典來跟蹤代碼中的每個Python計時器。

我們擴展download_data.py腳本。

#?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'}
????for?i?in?range(10):
????????res?=?requests.get(source_url,?headers=headers)
????????with?open('dataset/datasets.zip',?'wb')?as?f:
????????????f.write(res.content)
????t.stop()
if?__name__=="__main__":
????main()

這段代碼的一個微妙問題是,不僅要測量下載數(shù)據(jù)所需的時間,還要測量 Python 存儲數(shù)據(jù)到磁盤所花費的時間。這可能并重要,有時候這兩者所花費的時間可以忽略不計。但還是希望有一種方法可以精確地計時沒一個步驟,將會更好。

有幾種方法可以在不改變Timer當前實現(xiàn)的情況下解決這個問題,且只需要幾行代碼即可實現(xiàn)。

首先,將引入一個名為.timers的字典作為Timer的類變量,此時Timer的所有實例將共享它。通過在任何方法之外定義它來實現(xiàn)它:

class?Timer:
????timers?=?{}

類變量可以直接在類上訪問,也可以通過類的實例訪問:

>>>?from?timer?import?Timer
>>>?Timer.timers
{}

>>>?t?=?Timer()
>>>?t.timers
{}

>>>?Timer.timers?is?t.timers
True

在這兩種情況下,代碼都返回相同的空類字典。

接下來向 Python 計時器添加可選名稱??梢詫⒃撁Q用于兩種不同的目的:

  • 在代碼中查找經(jīng)過的時間
  • 累加同名定時器

要向Python計時器添加名稱,需要對 timer.py 進行更改。首先,Timer 接受 name 參數(shù)。第二,當計時器停止時,運行時間應該添加到 .timers 中:

#?timer.py
#?...
class?Timer:
????timers?=?{}
????def?__init__(
????????self,
????????name=None,
????????text="Elapsed?time:?{:0.4f}?seconds",
????????logger=print,
????):
????????self._start_time?=?None
????????self.name?=?name
????????self.text?=?text
????????self.logger?=?logger

????????#?向計時器字典中添加新的命名計時器
????????if?name:
????????????self.timers.setdefault(name,?0)

????#?其他方法保持不變
????
????def?stop(self):
????????"""Stop?the?timer,?and?report?the?elapsed?time"""
????????if?self._start_time?is?None:
????????????raise?TimerError(f"Timer?is?not?running.?Use?.start()?to?start?it")
????????elapsed_time?=?time.perf_counter()?-?self._start_time
????????self._start_time?=?None
????????if?self.logger:
????????????self.logger(self.text.format(elapsed_time))
????????if?self.name:
????????????self.timers[self.name]?+=?elapsed_time
????????return?elapsed_time

注意,在向.timers中添加新的Python計時器時,使用了.setdefault()方法。它只在沒有在字典中定義name的情況下設置值,如果name已經(jīng)在.timers中使用,那么該值將保持不變,此時可以積累幾個計時器:

>>>?from?timer?import?Timer
>>>?t?=?Timer("accumulate")
>>>?t.start()

>>>?t.stop()??#?A?few?seconds?later
Elapsed?time:?3.7036?seconds
3.703554293999332

>>>?t.start()

>>>?t.stop()??#?A?few?seconds?later
Elapsed?time:?2.3449?seconds
2.3448921170001995

>>>?Timer.timers
{'accumulate':?6.0484464109995315}

現(xiàn)在可以重新訪問download_data.py并確保僅測量下載數(shù)據(jù)所花費的時間:

#?download_data.py
import?requests
from?timer?import?Timer
def?main():
????t?=?Timer("download",?logger=None)
????source_url?=?'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'
????headers?=?{'User-Agent':?'Mozilla/5.0'}
????for?i?in?range(10):
????????t.start()
????????res?=?requests.get(source_url,?headers=headers)
????????t.stop()
????????with?open('dataset/datasets.zip',?'wb')?as?f:
????????????f.write(res.content)
????download_time?=?Timer.timers["download"]
????print(f"Downloaded?10?dataset?in?{download_time:0.2f}?seconds")
????
if?__name__=="__main__":
????main()

現(xiàn)在你有了一個非常簡潔的版本,Timer它一致、靈活、方便且信息豐富!也可以將本節(jié)中所做的許多改進應用于項目中的其他類型的類。

Timer改進

最后一個改進Timer,以交互方式使用它時使其更具信息性。下面操作是實例化一個計時器類,并查看其信息:

>>>?from?timer?import?Timer
>>>?t?=?Timer()
>>>?t
<timer.Timer?object?at?0x7f0578804320>

最后一行是 Python 表示對象的默認方式。我們從這個結果中看到的信息,并不是很明確,我們接下來對其進行改進。

這里介紹一個 dataclasses 類,該類僅包含在 Python 3.7 及更高版本中。

pip?install?dataclasses

可以使用@dataclass裝飾器將 Python 計時器轉換為數(shù)據(jù)類

#?timer.py
import?time
from?dataclasses?import?dataclass,?field
from?typing?import?Any,?ClassVar
#?...
@dataclass
class?Timer:
????timers:?ClassVar?=?{}
????name:?Any?=?None
????text:?Any?=?"Elapsed?time:?{:0.4f}?seconds"
????logger:?Any?=?print
????_start_time:?Any?=?field(default=None,?init=False,?repr=False)

????def?__post_init__(self):
????????"""Initialization:?add?timer?to?dict?of?timers"""
????????if?self.name:
????????????self.timers.setdefault(self.name,?0)

????#?其余代碼不變

此代碼替換了之前的 .__init__() 方法。請注意數(shù)據(jù)類如何使用類似于之前看到的用于定義所有變量的類變量語法的語法。事實上,.__init__()是根據(jù)類定義中的注釋變量自動為數(shù)據(jù)類創(chuàng)建的。

如果需要注釋變量以使用數(shù)據(jù)類??梢允褂么俗⒔庀虼a添加類型提示。如果不想使用類型提示,那么可以使用 Any 來注釋所有變量。接下來我們很快就會學習如何將實際類型提示添加到我們的數(shù)據(jù)類中。

以下是有關 Timer 數(shù)據(jù)類的一些注意事項:

  • 第 6 行:@dataclass 裝飾器將 Timer 定義為數(shù)據(jù)類。
  • 第 8 行:數(shù)據(jù)類需要特殊的 ClassVar 注釋來指定 .timers 是一個類變量。
  • 第 9 到 11 行:.name、.text 和 .logger 將被定義為 Timer 上的屬性,可以在創(chuàng)建 Timer 實例時指定其值。它們都有給定的默認值。
  • 第 12 行:回想一下 ._start_time 是一個特殊屬性,用于跟蹤 Python 計時器的狀態(tài),但它應該對用戶隱藏。使用 dataclasses.field(), ._start_time 應該從 .__init__() 和 Timer 的表示中刪除。
  • 除了設置實例屬性之外,可以使用特殊的 .__post_init__() 方法進行初始化。這里使用它將命名的計時器添加到 .timers。

新 Timer 數(shù)據(jù)類與之前的常規(guī)類使用功能一樣,但它現(xiàn)在有一個很好的信息表示

from?timer?import?Timer
t?=?Timer()
t
Timer(name=None, 
  text='Elapsed time: {:0.4f} seconds',
  logger=<built-in function print>)
t.start()
#?幾秒鐘后
t.stop()

Elapsed time: 6.7197 seconds
6.719705373998295

總結

現(xiàn)在我們有了一個非常簡潔的 Timer 版本,它一致、靈活、方便且信息豐富!我們還可以將本文中所做的許多改進應用于項目中的其他類型的類。

現(xiàn)在我們訪問當前的完整源代碼Timer。會注意到在代碼中添加了類型提示以獲取額外的文檔:

#?timer.py

from?dataclasses?import?dataclass,?field
import?time
from?typing?import?Callable,?ClassVar,?Dict,?Optional

class?TimerError(Exception):
????"""A?custom?exception?used?to?report?errors?in?use?of?Timer?class"""

@dataclass
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:
????????"""Add?timer?to?dict?of?timers?after?initialization"""
????????if?self.name?is?not?None:
????????????self.timers.setdefault(self.name,?0)

????def?start(self)?->?None:
????????"""Start?a?new?timer"""
????????if?self._start_time?is?not?None:
????????????raise?TimerError(f"Timer?is?running.?Use?.stop()?to?stop?it")

????????self._start_time?=?time.perf_counter()

????def?stop(self)?->?float:
????????"""Stop?the?timer,?and?report?the?elapsed?time"""
????????if?self._start_time?is?None:
????????????raise?TimerError(f"Timer?is?not?running.?Use?.start()?to?start?it")

????????#?Calculate?elapsed?time
????????elapsed_time?=?time.perf_counter()?-?self._start_time
????????self._start_time?=?None

????????#?Report?elapsed?time
????????if?self.logger:
????????????self.logger(self.text.format(elapsed_time))
????????if?self.name:
????????????self.timers[self.name]?+=?elapsed_time

????????return?elapsed_time

總結下: 使用類創(chuàng)建 Python 計時器有幾個好處:

  • 可讀性:仔細選擇類和方法名稱,你的代碼將更自然地閱讀。
  • 一致性:將屬性和行為封裝到屬性和方法中,你的代碼將更易于使用。
  • 靈活性:使用具有默認值而不是硬編碼值的屬性,你的代碼將是可重用的。

這個類非常靈活,幾乎可以在任何需要監(jiān)控代碼運行時間的情況下使用它。但是,在接下來的部分中,云朵君將和大家一起了解如何使用上下文管理器和裝飾器,這將更方便地對代碼塊和函數(shù)進行計時。

以上就是手把手帶你用Python實現(xiàn)一個計時器的詳細內容,更多關于Python計時器的資料請關注腳本之家其它相關文章!

相關文章

最新評論