您现在的位置是:网站首页> 编程资料编程资料

手把手带你用Python实现一个计时器_python_

2023-05-26 436人已围观

简介 手把手带你用Python实现一个计时器_python_

虽然许多数据工作者认为 Python 是一种有效的编程语言,但纯 Python 程序比C、Rust 和 Java 等编译语言中的对应程序运行得更慢,为了更好地监控和优化Python程序,云朵君将和大家一起学习如何使用 Python 计时器来监控程序运行的速度,以便正对性改善代码性能。

为了更好地掌握 Python 计时器的应用,我们后面还补充了有关Python类、上下文管理器和装饰器的背景知识。因篇幅限制,其中利用上下文管理器和装饰器优化 Python 计时器,将在后续文章学习,不在本篇文章范围内。

Python 计时器

首先,我们向某段代码中添加一个Python 计时器以监控其性能。

Python 定时器函数

Python 中的内置time[1]模块中有几个可以测量时间的函数:

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

Python 3.7 引入了几个新函数,如thread_time()[2],以及上述所有函数的纳秒版本,以_ns后缀命名。例如,perf_counter_ns()perf_counter()的纳秒版本的。

perf_counter()返回性能计数器的值(以秒为单位),即具有最高可用分辨率的时钟以测量短持续时间。

首先,使用perf_counter()创建一个 Python 计时器。将把它与其他 Python 计时器函数进行比较,看看 perf_counter() 的优势。

示例

创建一个脚本,定义一个简短的函数:从清华云上下载一组数据。

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 计时器来监控该脚本的性能。

第一个 Python 计时器

现在使用函数time.perf_counter()函数创建一个计时器,这是一个非常适合针对部分代码的性能计时的计数器。

perf_counter()从某个未指定的时刻开始测量时间(以秒为单位),这意味着对该函数的单个调用的返回值没有用。但当查看对perf_counter()两次调用之间的差异时,可以计算出两次调用之间经过了多少秒。

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

在此示例中,两次调用 perf_counter() 相隔近 19 秒。可以通过计算两个输出之间的差异来确认这一点:413.31714087 - 394.540232282 = 18.78

现在可以将 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()函数中 f 字符串前面的表示这是一个 f-string ,这是格式化文本字符串的较为便捷的方式。:0.4f是一个格式说明符,表示数字,toc - tic应打印为带有四位小数的十进制数。

运行程序可以看到程序经过的时间:

该程序耗时: 0.026 seconds

就是这么简单。接下来我们一起学习如何将 Python 计时器包装到一个类、一个上下文管理器和一个装饰器中,这样可以更加一致和方便使用计时器。

一个 Python 定时器类

这里我们至少需要一个变量来存储 Python 计时器的状态。接下来我们创建一个与手动调用 perf_counter() 相同的类,但更具可读性和一致性。

创建和更新Timer类,使用该类以多种不同方式对代码进行计时。

$ python -m pip install codetiming 

理解 Python 中的类

Class是面向对象编程的主要构建块。类本质上是一个模板,可以使用它来创建对象

在 Python 中,当需要对需要跟踪特定状态的事物进行建模时,类非常有用。一般来说,类是属性的集合,称为属性,以及行为,称为方法

创建 Python 计时器类

类有利于跟踪状态。在Timer类中,想要跟踪计时器何时开始以及已经多少时间。对于Timer类的第一个实现,将添加一个._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") 

这里我们需要花点时间仔细地浏览代码,会发现一些不同的事情。

首先定义了一个TimerError Python 类。该(Exception)符号表示TimerError 继承自另一个名为Exception的父类。使用这个内置类进行错误处理。不需要向TimerError添加任何属性或方法,但自定义错误可以更灵活地处理Timer内部问题。

接下来自定义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 属性。为方便起见,我们还可以提供合理的默认值。

要添加.textTimer实例变量,可执行以下操作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__(    
                
                

-六神源码网