虽然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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。