Heim  >  Artikel  >  Backend-Entwicklung  >  Bringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Python-Timer implementieren

Bringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Python-Timer implementieren

王林
王林nach vorne
2023-04-14 16:04:032269Durchsuche

Um die Anwendung von Python-Timern besser zu beherrschen, haben wir auch Hintergrundwissen zu Python-Klassen, Kontextmanagern und Dekoratoren hinzugefügt. Aus Platzgründen wird die Verwendung von Kontextmanagern und Dekoratoren zur Optimierung von Python-Timern in nachfolgenden Artikeln untersucht und ist nicht Gegenstand dieses Artikels.

Bringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Python-Timer implementieren

Python-Timer

Zuerst fügen wir einem Codestück einen Python-Timer hinzu, um dessen Leistung zu überwachen.

Python-Timer-Funktion

Das integrierte Zeitmodul[1] in Python verfügt über mehrere Funktionen, die Zeit messen können:

    #🎜🎜 # monotonic()
  • perf_counter()
  • process_time()
  • time()
Python 3.7 führte mehrere neue Funktionen ein, wie etwa thread_time()[2] und Nanosekundenversionen aller oben genannten Funktionen, benannt mit dem Suffix _ns​. Beispielsweise ist perf_counter_ns()​ die Nanosekundenversion von perf_counter().

perf_counter()

Gibt den Wert in Sekunden des Leistungszählers zurück, d. h. der Uhr mit der höchsten verfügbaren Auflösung zur Messung kurzer Dauern.

Verwenden Sie zunächst perf_counter()​, um einen Python-Timer zu erstellen. Ich werde es mit anderen Python-Timerfunktionen vergleichen, um die Vorteile von perf_counter() zu sehen.

Beispiel

Erstellen Sie ein Skript und definieren Sie eine kurze Funktion: Laden Sie einen Datensatz aus der Tsinghua Cloud herunter.

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()

Wir können Python-Timer verwenden, um die Leistung dieses Skripts zu überwachen.

Der erste Python-Timer

Verwenden Sie nun die Funktion time.perf_counter()​, um einen Timer zu erstellen. Dabei handelt es sich um einen Zähler, der sich sehr gut für das Leistungstiming eines Teils von eignet der Code.

perf_counter()​Misst die Zeit in Sekunden ab einem unbestimmten Zeitpunkt, was bedeutet, dass der Rückgabewert eines einzelnen Aufrufs dieser Funktion nutzlos ist. Wenn Sie sich jedoch den Unterschied zwischen zwei Aufrufen von perf_counter() ansehen, können Sie berechnen, wie viele Sekunden zwischen den beiden Aufrufen vergangen sind.

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

>>> time.perf_counter()# 几秒钟后
413.31714087

In diesem Beispiel liegen die beiden Aufrufe von perf_counter() fast 19 Sekunden auseinander. Dies kann durch die Berechnung der Differenz zwischen den beiden Ausgaben bestätigt werden: 413,31714087 – 394,540232282 = 18,78.

Es ist jetzt möglich, dem Beispielcode einen Python-Timer hinzuzufügen:

# 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()

Beachten Sie, dass perf_counter() die gesamte Ausführungszeit des Programms ausgibt, indem die Differenz zwischen den beiden berechnet wird ruft Zeit.

print()​Die f​-Zeichenfolge vor der Funktion zeigt an, dass es sich um eine f-Zeichenfolge handelt, was eine bequemere Möglichkeit zum Formatieren von Textzeichenfolgen darstellt. :0.4f​ ist ein Formatbezeichner, der eine Zahl darstellt, die als Dezimalzahl mit vier Dezimalstellen ausgegeben werden soll.

Führen Sie das Programm aus und Sie können die verstrichene Zeit sehen:

该程序耗时: 0.026 seconds

So einfach ist das. Als Nächstes lernen wir, wie man einen Python-Timer in eine Klasse, einen Kontextmanager und einen Dekorator einbindet (zwei nachfolgende Artikel dieser Reihe, die aktualisiert werden), damit der Timer konsistenter und bequemer verwendet werden kann.

Eine Python-Timer-Klasse

Hier benötigen wir mindestens eine Variable, um den Status des Python-Timers zu speichern. Als Nächstes erstellen wir eine Klasse, die mit dem manuellen Aufruf von perf_counter() identisch ist, jedoch besser lesbar und konsistenter ist.

Erstellen und aktualisieren Sie die Timer-Klasse und verwenden Sie sie, um Ihren Code auf viele verschiedene Arten zu timen.

$ python -m pip install codetiming

Klassen in Python verstehen

Klassen sind die Hauptbausteine ​​der objektorientierten Programmierung. Eine Klasse ist im Wesentlichen eine Vorlage, die zum Erstellen von Objekten verwendet werden kann.

In Python sind Klassen sehr nützlich, wenn Sie etwas modellieren müssen, das einen bestimmten Zustand verfolgen muss. Im Allgemeinen ist eine Klasse eine Sammlung von Attributen, die als Eigenschaften bezeichnet werden, und Verhaltensweisen, die als Methoden bezeichnet werden.

Das Erstellen einer Python-Timer-Klasse

Klasse ist nützlich, um den Status zu verfolgen. In der Timer-Klasse möchten Sie verfolgen, wann der Timer startet und wie viel Zeit verstrichen ist. Für die erste Implementierung der Timer-Klasse werden ein ._start_time​-Attribut und die Methoden .start()​ und .stop()​ hinzugefügt. Fügen Sie den folgenden Code zu einer Datei namens timer.py hinzu:

# 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")

Hier müssen wir uns einen Moment Zeit nehmen, um den Code sorgfältig durchzusehen, und wir werden ein paar verschiedene Dinge finden.

Definieren Sie zunächst eine TimerError​-Python-Klasse. Das (Exception)-Symbol gibt an, dass TimerError von einer anderen übergeordneten Klasse namens Exception erbt. Verwenden Sie diese integrierte Klasse zur Fehlerbehandlung. Es ist nicht erforderlich, TimerError irgendwelche Eigenschaften oder Methoden hinzuzufügen, aber benutzerdefinierte Fehler bieten mehr Flexibilität bei der Behandlung interner Timer-Probleme.

接下来自定义Timer​类。当从一个类创建或实例化一个对象时,代码会调用特殊方法.__init__()​初始化实例。在这里定义的第一个Timer​版本中,只需初始化._start_time​属性,将用它来跟踪 Python 计时器的状态,计时器未运行时它的值为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()的示例进行比较。代码的结构相似,但现在代码更清晰了,这也是使用类的好处之一。通过仔细选择类、方法和属性名称,可以使你的代码非常具有描述性!

使用 Python 计时器类

现在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还负责将经过的时间打印到控制台,使得所用时间的记录更加一致。运行代码时,得到的输出几乎相同:

Elapsed time: 0.502 seconds
...

打印经过的时间Timer可能是一致的,但这种方法好像不是很灵活。下面我们添加一些更加灵活的东西到代码中。​

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

到目前为止,我们已经了解到类适用于我们想要封装状态并确保代码一致性的情况。在本节中,我们将一起给 Python 计时器加入更多便利性和灵活性,那​怎么做呢?

  • 在报告消耗的时间时,使用可调整的文本和格式
  • 将日志记录​打印到控制台、写入到​日志文件或程序的其他部分
  • 创建一个可以在多次调用中可积累的Python计时器
  • 构建 Python 计时器的信息表示

首先,自定义用于报告所用时间的文本。在前面的代码中,文本 f"Elapsed time: {elapsed_time:0.4f} seconds"​ 被生​硬编码到 .stop() ​中。如若想使得类代码更加灵活, 可以使用实例变量,其值通常作为参数传递给.__init__()​并存储到 self 属性。为方便起见,我们还可以提供合理的默认值。

要添加.text​为Timer​实例变量,可执行以下操作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"​是作为一个常规字符串给出的,而不是f-string​。这里不能使用f-string​,因为f-string会立即计算,当你实例化Timer时,你的代码还没有计算出消耗的时间。

注意: 如果要使用f-string​来指定.text,则需要使用双花括号来转义实际经过时间将替换的花括号。

如: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

接下来,我们不只是想将消息打印到控制台,还想保存时间测量结果,这样可以便于将它们存储在数据库中。可以通过从.stop()​返回elapsed_time的值来实现这一点。然后,调用代码可以选择忽略该返回值或保存它以供以后处理。

如果想要将Timer集成到日志logging中。要支持计时器的日志记录或其他输出,需要更改对print()的调用,以便用户可以提供自己的日志记录函数。这可以用类似于你之前定制的文本来完成:

# 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()​,而是创建另一个实例变量 self.logger​,引用一个接受字符串作为参数的函数。除此之外,还可以对文件对象使用logging.info()​或.write()​等函数。还要注意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

接下来第三个改进是积累时间度量的能力。例如,在循环中调用一个慢速函数时,希望以命名计时器的形式添加更多的功能,并使用一个字典来跟踪代码中的每个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()

这段代码的一个微妙问题是,不仅要测量下载数据所需的时间,还要测量 Python 存储数据到磁盘所花费的时间。这可能并重要,有时候这两者所花费的时间可以忽略不计。但还是希望有一种方法可以精确地计时没一个步骤,将会更好。

有几种方法可以在不改变Timer当前实现的情况下解决这个问题,且只需要几行代码即可实现。

首先,将引入一个名为.timers的字典作为Timer的类变量,此时Timer的所有实例将共享它。通过在任何方法之外定义它来实现它:

class Timer:
timers = {}

类变量可以直接在类上访问,也可以通过类的实例访问:

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

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

>>> Timer.timers is t.timers
True

在这两种情况下,代码都返回相同的空类字典。

接下来向 Python 计时器添加可选名称。可以将该名称用于两种不同的目的:

  1. 在代码中查找经过的时间
  2. 累加同名定时器

要向Python计时器添加名称,需要对 timer.py​ 进行更改。首先,Timer 接受 name 参数。第二,当计时器停止时,运行时间应该添加到 .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已经在.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}

现在可以重新访问download_data.py并确保仅测量下载数据所花费的时间:

# 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()

现在你有了一个非常简洁的版本,Timer它一致、灵活、方便且信息丰富!也可以将本节中所做的许多改进应用于项目中的其他类型的类。

Timer改进

最后一个改进Timer,以交互方式使用它时使其更具信息性。下面操作是实例化一个计时器类,并查看其信息:

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

最后一行是 Python 表示对象的默认方式。我们从这个结果中看到的信息,并不是很明确,我们接下来对其进行改进。

这里介绍一个 dataclasses 类,该类仅包含在 Python 3.7 及更高版本中。

pip install dataclasses

可以使用@dataclass装饰器将 Python 计时器转换为数据类

# 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__() ​方法。请注意数据类如何使用类似于之前看到的用于定义所有变量的类变量语法的语法。事实上,.__init__()是根据类定义中的注释变量自动为数据类创建的。

如果需要注释变量以使用数据类。可以使用此注解向代码添加类型提示。如果不想使用类型提示,那么可以使用 Any 来注释所有变量。接下来我们很快就会学习如何将实际类型提示添加到我们的数据类中。

以下是有关 Timer 数据类的一些注意事项:

  • 第 6 行:@dataclass 装饰器将Timer 定义为数据类。
  • 第 8 行:数据类需要特殊的 ClassVar 注释来指定.timers 是一个类变量。
  • 第 9 到 11 行:.name​、.text​ 和.logger 将被定义为 Timer 上的属性,可以在创建 Timer 实例时指定其值。它们都有给定的默认值。
  • 第 12 行:回想一下._start_time​ 是一个特殊属性,用于跟踪 Python 计时器的状态,但它应该对用户隐藏。使用dataclasses.field()​, ._start_time​ 应该从.__init__() 和 Timer 的表示中删除。
  • 除了设置实例属性之外,可以使用特殊的 .__post_init__()​ 方法进行初始化。这里使用它将命名的计时器添加到 .timers。

新 Timer 数据类与之前的常规类使用功能一样,但它现在有一个很好的信息表示:

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

总结

现在我们有了一个非常简洁的 Timer 版本,它一致、灵活、方便且信息丰富!我们还可以将本文中所做的许多改进应用于项目中的其他类型的类。

现在我们访问当前的完整源代码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

总结下: 使用类创建 Python 计时器有几个好处:

  • 可读性:仔细选择类和方法名称,你的代码将更自然地阅读。
  • 一致性:将属性和行为封装到属性和方法中,你的代码将更易于使用。
  • 灵活性:使用具有默认值而不是硬编码值的属性,你的代码将是可重用的。

这个类非常灵活,几乎可以在任何需要监控代码运行时间的情况下使用它。但是,在接下来的部分中,云朵君将和大家一起了解如何使用上下文管理器和装饰器,这将更方便地对代码块和函数进行计时。

Das obige ist der detaillierte Inhalt vonBringen Sie Ihnen Schritt für Schritt bei, wie Sie einen Python-Timer implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:51cto.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen