这里的其他评级较高的答案可能是不正确的
正如@Mercury所指出的那样,虽然@Vlad Bezden提供的另一个顶级答案在技术上看起来很好,但由于t()
产生的值也可能受到with
语句外部执行的代码的影响,因此在技术上是不正确的。例如,如果在with
语句之后但在print
语句之前运行time.sleep(5)
,那么在打印语句中调用t()
将给出大约6秒的结果,而不是大约1秒。
在某些情况下,可以通过将打印命令插入到上下文管理器中来避免这种情况,示例如下:
from time import perf_counter
from contextlib import contextmanager
@contextmanager
def catchtime() -> float:
start = perf_counter()
yield lambda: perf_counter() - start
print(f'Time: {perf_counter() - start:.3f} seconds')
然而,即使有这个修改,注意到稍后运行sleep(5)会导致打印出错误的时间:
from time import sleep
with catchtime() as t:
sleep(1)
sleep(5)
print(f'Time: {t():.3f} seconds')
解决方案#1:上下文管理器方法(修复版)
此解决方案使用两个参考点t1
和t2
来捕获时间差异。通过确保只有当上下文管理器退出时t2
才会更新,即使在with
块后存在延迟或操作,上下文内的经过时间仍然保持一致。
以下是其工作原理:
进入阶段:当进入上下文管理器时,同时用当前时间戳初始化t1
和t2
。这确保它们的差异最初为零。
在上下文中:此阶段不对t1
或t2
进行任何更改。因此,它们的差异保持为零。
退出阶段:只有当上下文管理器退出时,t2
才会更新为当前时间戳。这一步“锁定”了结束时间。然后,t2 - t1
表示仅在上下文内经过的时间。
from time import perf_counter
from time import sleep
from contextlib import contextmanager
@contextmanager
def catchtime() -> float:
t1 = t2 = perf_counter()
yield lambda: t2 - t1
t2 = perf_counter()
with catchtime() as t:
sleep(1)
sleep(5)
print(f'Time: {t():.3f} seconds')
使用这种方法,
with
块外的操作或延迟不会扭曲时间测量。与本页上其他最受欢迎的答案不同,这种方法引入了一种间接性,您可以在退出上下文管理器时明确捕获结束时间戳。这一步有效地“冻结”了结束时间,防止它在上下文之外更新。
解决方案#2:基于类的方法(灵活)
这种方法建立在@BrenBarn的想法基础上,但增加了一些可用性改进:
自动计时打印输出:一旦上下文中的代码块完成,经过的时间将自动打印出来。要禁用此功能,可以删除print(self.readout)
行。
存储格式化输出:经过的时间以格式化字符串的形式存储在self.readout
中,可以在任何后续时间检索和打印。
原始经过时间:经过的时间以秒为单位(作为float
)存储在self.time
中,以供可能的进一步使用或计算。
from time import perf_counter
class catchtime:
def __enter__(self):
self.start = perf_counter()
return self
def __exit__(self, type, value, traceback):
self.time = perf_counter() - self.start
self.readout = f'Time: {self.time:.3f} seconds'
print(self.readout)
与解决方案#1相同,即使在上下文管理器之后有操作(如sleep(5)
),也不会影响捕获的经过时间。
from time import sleep
with catchtime() as timer:
sleep(1)
sleep(5)
print(timer.time)
sleep(5)
print(timer.readout)
这种方法在访问和利用经过的时间方面提供了灵活性,既可以作为原始数据,也可以作为格式化的字符串。