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

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

虽然Python是一种有效的编程语言,但是纯Python程序的运行速度比C、Rust、Java等编译语言的相应程序要慢。为了更好地监控和优化Python程序,今天,我们将向您展示如何使用Python timer来监控程序的运行速度,从而提高代码性能。

目录

Python Timer Python Timer函数示例第一个Python Timer一个Python Timer类理解Python中的类创建Python Timer类使用Python Timer类增加更多的便利性和灵活性Timer改进总结虽然很多数据工作者认为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()的优势。

示例

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

导入请求

defmain():

source _ URL=' https://cloud . Tsinghua . edu . cn/d/E1 ccfff 39 ad 541908 BAE/files/?p=/all_six_datasets.zipdl=1 '

headers={ ' User-Agent ':' Mozilla/5.0 ' }

res=requests.get(source_url,headers=headers)

withopen('dataset/datasets.zip ',' wb')asf:

f.write(内容)

if__name__=='__main__ ':

主()

我们可以使用Python timer来监控这个脚本的性能。

第一个 Python 计时器

现在使用函数time.perf_counter()函数创建一个计时器,非常适合用来计时某些代码的性能。

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

进口时间

time.perf _计数器()

394.540232282

Time.perf_counter()#几秒钟后

413.31714087

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

现在,您可以将Python timer添加到示例代码中:

#下载_数据. py

导入请求

进口时间

defmain():

tic=time.perf_counter()

source _ URL=' https://cloud . Tsinghua . edu . cn/d/E1 ccfff 39 ad 541908 BAE/files/?p=/all_six_datasets.zipdl=1 '

headers={ ' User-Agent ':' Mozilla/5.0 ' }

res=requests.get(source_url,headers=headers)

withopen('dataset/datasets.zip ',' wb')asf:

f.write(内容)

toc=time.perf_counter()

打印(f '此程序需要时间:{ TOC-TIC:0.4f }秒')

if__name__=='__main__ ':

主()

请注意,perf_counter()通过计算两次调用之间的差异来打印运行整个程序所花费的时间。

print()函数中F字符串前面的表达式表示这是一个F字符串,这是一种格式化文本字符串的便捷方式。0.4f是格式说明符,表示一个数字。toc-tic应该打印成一个有四个小数位的十进制数。

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

本程序耗时:0.026秒

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

一个 Python 定时器类

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

并创建和更新Timer类,该类可用于以多种不同方式为代码计时。

$python-mpipinstallcodetiming

理解 Python 中的类

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

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

创建 Python 计时器类

类有利于跟踪状态。在Timer类中,您希望跟踪计时器何时启动以及已经过去了多长时间。对于Timer类的第一个实现,需要一个. _start_time属性和。开始()和。将添加stop()方法。将以下代码添加到名为timer.py的文件中:

#timer.py

进口时间

classTimerError(异常):

''使用计时器类时报告错误的自定义异常'''

classTimer:

def__init__(self):

自我。_ start _ time=无

defstart(自身):

' ' ' Startanewtimer ' ' '

如果自己。_start_timeisnotNone:

raiseTimerError(f ' timerisrunning . use . stop()to stopit ')

自我。_ start _ time=time.perf _计数器()

定义停止(自身):

' ' ' Stopthetimer,并报告theelapsedtime ' ' '

如果自己。_start_timeisNone:

raiseTimerError(f ' timerisnotrunning . use . start()to startit ')

elapsed _ time=time . perf _ counter()-self。_开始时间

自我。_ start _ time=无

打印(f ' elapsed time:{ elapsed _ time:0.4f }秒)

这里我们需要花点时间仔细浏览一下代码,会发现一些不一样的东西。

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

接下来,定制计时器类。当从类中创建对象时,代码调用特殊的方法。__init__()初始化实例。在这里定义的第一个计时器版本中,只需初始化。_start_time属性,它将用于跟踪Python计时器的状态。当计时器不运行时,其值为None。计时器运行后,用它来跟踪计时器的开始时间。

实例化._ start _ time的第一个下划线(_)前缀是Python约定。意思是。_start_time是Timer类的用户不应该操作的内部属性。

打电话时。start()要启动一个新的Python计时器,首先检查计时器是否正在运行。则perf_counter()的当前值存储在。_开始时间。

另一方面,当。调用stop()时,首先检查Python定时器是否在运行。如果是,将运行时间计算为perf_counter()的当前值与中存储的值之差。_开始时间。最后,重置。_start_time重新启动计时器并打印运行时间。

下面是如何使用计时器方法:

fromtimerimportTimer

t=定时器()

启动()

#几秒钟后

t .停止()

耗时:3.8191秒

直接使用perf_counter()将此示例与前面的示例进行比较。的代码结构类似,但现在代码更加清晰,这是使用类的好处之一。通过仔细选择类名、方法名和属性名,你可以使你的代码更具描述性!

使用 Python 计时器类

现在在Timer类中编写download_data.py。只需对前面的代码做一些修改:

#下载_数据. py

导入请求

fromtimerimportTimer

defmain():

t=定时器()

启动()

source _ URL=' https://cloud . Tsinghua . edu . cn/d/E1 ccfff 39 ad 541908 BAE/files/?p=/all_six_datasets.zipdl=1 '

headers={ ' User-Agent ':' Mozilla/5.0 ' }

res=requests.get(source_url,headers=headers)

withopen('dataset/datasets.zip ',' wb')asf:

f.write(内容)

t .停止()

if__name__=='__main__ ':

主()

请注意,这段代码与之前使用的代码非常相似。除了使代码可读性更好之外,Timer还负责将经过的时间打印到控制台,使所用时间的记录更加一致。当您运行代码时,您会得到几乎相同的输出:

耗时:0.502秒

.

经过的时间计时器可能是一致的,但这种方法似乎不是很灵活。让我们给代码添加一些更灵活的东西。

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

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

在报告消耗的时间时,使用注意:可调整的文本和格式打印到控制台,写入日志文件或程序的其他部分来创建一个Python计时器,该计时器可以在多个调用中为日志记录以构建一个Python计时器。

首先,定制用于报告花费时间的文本。在前面的代码中,文本f ' elapsed time:{ elapsed _ time:0.4f } seconds '被生硬地编码为。停止()。如果希望使类代码更灵活,可积累,它的值通常作为参数传递给。__init__()并存储在self属性中。为了方便起见,我们还可以提供合理的默认值。

补充一下。文本作为计时器实例变量,您可以执行以下操作:

#timer.py

def__init__(self,text=' elapsed time:{:0.4f }秒'):

自我。_ start _ time=无

self.text=text

请注意,默认文本“运行时间:{:0.4f}秒”是作为常规字符串而不是f字符串给出的。这里不能用f-string,因为f-string会马上计算。当您实例化Timer时,您的代码还没有计算消耗的时间。

信息表示如果你想用f字符串来指定。文本,您需要使用双花括号来转义将被实际运行时间替换的花括号。

例如f“在{{:0.4f}}秒内完成{task}”。如果task的值为“正在读取”,则该f字符串将被计算为“在{:0.4f}秒内完成读取”。

英寸stop(),文本被用作模板,该模板用。format()方法:

#timer.py

定义停止(自身):

' ' ' Stopthetimer,并报告theelapsedtime ' ' '

如果自己。_start_timeisNone:

raiseTimerError(f ' timerisnotrunning . use . start()to startit ')

elapsed _ time=time . perf _ counter()-self。_开始时间

自我。_ start _ time=无

print(self . text . format(elapsed _ time))

timer.py更新后,您可以按如下方式更改文本:

fromtimerimportTimer

t=计时器(text='您等待了{: 1f }秒')

启动()

#几秒钟后

t .停止()

你等了4.1秒

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

如果你想把定时器集成到日志中。为了支持日志记录或计时器的其他输出,您需要更改对print()的调用,以便用户可以提供自己的日志记录功能。这可以通过类似于您之前自定义的文本来完成:

#timer.py

# .

classTimer:

def__init__(

自我,

text=' elapsed time:{:0.4f }秒',

记录器=打印

):

自我。_ start _ time=无

self.text=text

self.logger=logger

#其他方法保持不变

定义停止(自身):

' ' ' Stopthetimer,并报告theelapsedtime ' ' '

如果自己。_start_timeisNone:

raiseTimerError(f ' timerisnotrunning . use . start()to startit ')

elapsed _ time=time . perf _ counter()-self。_开始时间

自我。_ start _ time=无

ifself.logger:

self . logger(self . text . format(elapsed _ time))

returnelapsed_time

不要直接使用print(),而是创建另一个实例变量self.logger,它引用一个接受字符串作为参数的函数。除此之外,还可以使用logging.info()或。对文件对象写入()。还要注意,在if中,它允许通过传递logger=None来完全关闭打印。

以下两个示例展示了新功能的实际应用:

fromtimerimportTimer

导入日志记录

t=计时器(logger=logging.warning)

启动()

#几秒钟后

t.stop()#Afewsecondslater

警告:根:运行时间:3.1610秒

3.1609658249999484

t=定时器(记录器=无)

启动()

#几秒钟后

value=t.stop()

价值

4.710851433001153

接下来的第三个改进是可以使用实例变量。例如,在循环中调用慢速函数时,您希望以命名计时器的形式添加更多函数,并使用字典来跟踪代码中的每个Python计时器。

我们扩展注意:脚本。

#下载_数据. py

导入请求

fromtimerimportTimer

defmain():

t=定时器()

启动()

source _ URL=' https://cloud . Tsinghua . edu . cn/d/E1 ccfff 39 ad 541908 BAE/files/?p=/all_six_datasets.zipdl=1 '

headers={ ' User-Agent ':' Mozilla/5.0 ' }

foriinrange(10):

res=requests.get(source_url,headers=headers)

withopen('dataset/datasets.zip ',' wb')asf:

f.write(内容)

t .停止()

if__name__=='__main__ ':

主()

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

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

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

classTimer:

计时器={}

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

fromtimerimportTimer

计时器.计时器

{}

t=定时器()

计时器

{}

定时器.定时器列表.定时器

真实的

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

接下来,向Python计时器添加一个可选名称。该名称有两种不同的用途:

积累时间度量的能力运行时间download_data.py代码中具有相同名称的计时器

要向Python timer添加名称,需要对timer.py进行更改。首先,timer接受name参数。第二,当计时器停止时,运行时间应该加到。计时器:

#timer.py

# .

classTimer:

计时器={}

def__init__(

自我,

name=无,

text=' elapsed time:{:0.4f }秒',

记录器=打印,

):

自我。_ start _ time=无

self.name=name

self.text=text

self.logger=logger

#向计时器字典添加新的命名计时器

ifname:

self.timers.setdefault(名称,0)

#其他方法保持不变

定义停止(自身):

' ' ' Stopthetimer,并报告theelapsedtime ' ' '

如果自己。_start_timeisNone:

raiseTimerError(f ' timerisnotrunning . use . start()to startit ')

elapsed _ time=time . perf _ counter()-self。_开始时间

自我。_ start _ time=无

ifself.logger:

self . logger(self . text . format(elapsed _ time))

ifself.name:

self . timers[self . name]=elapsed _ time

returnelapsed_time

请注意。向添加新的Python计时器时,使用setdefault()方法。计时器。它只在字典中没有定义名称时设置值。如果该名称已经在中使用。计时器,则该值将保持不变。此时,可以累计几个定时器:

fromtimerimportTimer

t=计时器(“累计”)

启动()

t.stop()#Afewsecondslater

耗时:3.7036秒

3.703554293999332

启动()

t.stop()#Afewsecondslater

耗时:2.3449秒

2.3448921170001995

计时器.计时器

{ ' accumulate ':6.0484464109995315 }

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

#下载_数据. py

导入请求

fromtimerimportTimer

defmain():

t=计时器('下载',记录器=无)

source _ URL=' https://cloud . Tsinghua . edu . cn/d/E1 ccfff 39 ad 541908 BAE/files/?p=/all_six_datasets.zipdl=1 '

headers={ ' User-Agent ':' Mozilla/5.0 ' }

foriinrange(10):

启动()

res=requests.get(source_url,headers=headers)

t .停止()

withopen('dataset/datasets.zip ',' wb')asf:

f.write(内容)

download _ time=timer . timers[' download ']

打印(f ' downloaded 10 dataset in { download _ time:0.2f }秒)

if__name__=='__main__ ':

主()

现在你有一个非常简洁的版本,定时器,它是一致的,灵活的,方便的和丰富的!本节中所做的许多改进也可以应用于项目中的其他类型的类。

Timer改进

最后,改进了计时器,使其在交互使用时信息更加丰富。以下操作将实例化一个计时器类并查看其信息:

fromtimerimportTimer

t=定时器()

t

计时器。Timerobjectat0x7f0578804320

最后一行是Python默认的表示对象的方式。这个结果我们看到的信息不是很清楚,接下来我们会改进。

本文介绍了一个dataclasses类,它只包含在Python 3.7和更高版本中。

pipinstalldataclasses

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

#timer.py

进口时间

fromdataclassesimportdataclass,字段

fromtypingimportAny,ClassVar

# .

@dataclass

classTimer:

计时器:ClassVar={}

名称:任何=无

text:Any=' elapsed time:{:0.4f }秒'

记录器:Any=print

_start_time:Any=field(默认值=None,init=False,repr=False)

def__post_init__(self):

'''初始化:addtimertodictoftimers ' ' '

ifself.name:

self . timers . set default(self . name,0)

#其余代码保持不变。

这段代码取代了前面的代码。__init__()方法。请注意数据类是如何使用与类变量相似的语法来定义所有变量的。事实上。__init__()是根据类定义中的注释变量自动为数据类创建的。

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

以下是关于计时器数据类的一些注意事项:

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

新的Timer数据类使用与以前的常规类相同的函数,但它现在有一个良好的查找:

fromtimerimportTimer

t=定时器()

t

计时器(名称=无,

text='经过的时间:{:0.4f}秒',

记录器=内置函数打印)

启动()

#几秒钟后

t .停止()

耗时:6.7197秒

6.719705373998295

总结

现在我们有一个非常简洁的计时器版本,它是一致的,灵活的,方便的,信息丰富的!我们还可以将本文中的许多改进应用到项目中的其他类型的类中。

现在我们访问当前完整的源代码定时器。您会注意到代码中添加了一个类型提示,以获取附加文档:

#timer.py

fromdataclassesimportdataclass,字段

进口时间

fromtypingimportCallable,ClassVar,Dict,可选

classTimerError(异常):

' ' ' AcustomexceptionusedtoreporterrorsinuseofTimerclass ' ' '

@dataclass

classTimer:

计时器:ClassVar[Dict[str,float]]={}

name:可选[str]=无

text:str=' elapsed time:{:0.4f }秒'

记录器:可选[Callable[[str],None]]=print

_start_time:可选[float]=field(默认值=None,init=False,repr=False)

def _ _ post _ init _ _(self)-无:

' ' ' Addtimertodictoftimersafterinitialization ' ' '

ifself.nameisnotNone:

self . timers . set default(self . name,0)

defstart(自身)-无:

' ' ' Startanewtimer ' ' '

如果自己。_start_timeisnotNone:

raiseTimerError(f ' timerisrunning . use . stop()to stopit ')

自我。_ start _ time=time.perf _计数器()

defstop(自动)-浮动:

' ' ' Stopthetimer,并报告theelapsedtime ' ' '

如果自己。_start_timeisNone:

raiseTimerError(f ' timerisnotrunning . use . start()to startit ')

# Calculateelapsedtime

elapsed _ time=time . perf _ counter()-self。_开始时间

自我。_ start _ time=无

#Reportelapsedtime

ifself.logger:

self . logger(self . text . format(elapsed _ time))

ifself.name:

self . timers[self . name]=elapsed _ time

returnelapsed_time

累加使用类创建Python计时器有几个好处:

信息表示仔细选择类名和方法名,你的代码读起来会更自然。总结下:把属性和行为封装成属性和方法,你的代码会更容易使用。可读性:使用带有默认值的属性,而不是硬编码的值,你的代码将是可重用的。

这个类非常灵活,几乎可以在任何需要监控代码运行时间的情况下使用。但是,在下一部分,云云君将和你一起学习如何使用context manager和decorator,这将更容易对代码块和函数计时。

这就是用Python实现定时器的细节。关于Python定时器的更多信息,请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: