python的计时函数,python秒表计时器

  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 timer的应用,我们还增加了关于Python类、上下文管理器和装饰器.的背景知识由于篇幅的限制,Python timer通过使用上下文管理器和decorator进行了优化,这将在后面的文章中进行研究,不在本文讨论范围之内。

  

Python 计时器

  首先,我们给一段代码添加一个Python 计时器来监控它的性能。

  

Python 定时器函数

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

  Python 3.7在monotonic(),perf_counter(),process_time()引入了几个新函数,比如thread_time()[2],以及time()版本的所有上述函数,以_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()两次调用之间的差异时,可以计算出两次调用之间经过了多少秒。

  

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

  

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

  首先定义了一个TimerErrorPython 类。该(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__(

          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 计时器添加可选名称。可以将该名称用于两种不同的目的:

  

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

  要向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 计时器有几个好处:

  

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

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

  以上就是手把手带你用Python实现一个计时器的详细内容,更多关于Python计时器的资料请关注盛行IT软件开发工作室其它相关文章!

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

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