提问人:ArekBulski 提问时间:11/30/2015 更新时间:8/24/2023 访问量:19330
测量时间的 Python 上下文管理器
Python context manager that measures time
问:
我正在努力制作一段代码,允许测量在“with”语句中花费的时间,并将测量的时间(浮点数)分配给“with”语句中提供的变量。
import time
class catchtime:
def __enter__(self):
self.t = time.clock()
return 1
def __exit__(self, type, value, traceback):
return time.clock() - self.t
with catchtime() as t:
pass
这段代码留下了 clock() 调用之间的差异。如何处理这个问题?我需要一种方法从退出方法中分配一个新值。t=1
PEP 343 更详细地描述了 contect manager 的工作原理,但我不理解其中的大部分内容。
答:
解决了(几乎)。生成的变量是可强制的,可以转换为浮点数(但不是浮点数本身)。
class catchtime:
def __enter__(self):
self.t = time.clock()
return self
def __exit__(self, type, value, traceback):
self.e = time.clock()
def __float__(self):
return float(self.e - self.t)
def __coerce__(self, other):
return (float(self), other)
def __str__(self):
return str(float(self))
def __repr__(self):
return str(float(self))
with catchtime() as t:
pass
print t
print repr(t)
print float(t)
print 0+t
print 1*t
1.10000000001e-05
1.10000000001e-05
1.10000000001e-05
1.10000000001e-05
1.10000000001e-05
评论
catchtime
不能是 a,因为浮点数是不可变的,但您希望在上下文退出时更改它。您可以实现 float 接口(参见 )并(大部分)像 float 一样工作,但即便如此,当需要不可变对象(如键)时,您也会遇到问题。float
dir(float)
dict
你不能让它把你的时间分配给 。如 PEP 中所述,您在子句中指定的变量(如果有)被分配调用的结果,而不是 。换句话说,只在块的开头分配,而不是在块的末尾分配。t
as
__enter__
__exit__
t
with
你可以做的是改变你的,这样它就不会返回值,而是返回。然后,在块完成后,上下文管理器的属性将保存经过的时间。__exit__
self.t = time.clock() - self.t
with
t
要做到这一点,您还需要返回而不是 from 。不确定要通过使用 来实现什么。self
1
__enter__
1
所以它看起来像这样:
class catchtime(object):
def __enter__(self):
self.t = time.clock()
return self
def __exit__(self, type, value, traceback):
self.t = time.clock() - self.t
with catchtime() as t:
time.sleep(1)
print(t.t)
并且打印出一个非常接近 1 的值。
评论
as
with
with
with
t
t.t
.seconds
.total_seconds
下面是一个使用 contextmanager 的示例
from time import perf_counter
from contextlib import contextmanager
@contextmanager
def catchtime() -> float:
start = perf_counter()
yield lambda: perf_counter() - start
with catchtime() as t:
import time
time.sleep(1)
print(f"Execution time: {t():.4f} secs")
输出:
执行时间:1.0014秒
评论
start = perf_counter(); yield; print(perf_counter()-start);
t()
with
with
print
t()
这里其他最受好评的答案可能是不正确的
正如 @Mercury 所指出的,@Vlad Bezden 的另一个最佳答案虽然很巧妙,但在技术上是不正确的,因为 产生的值也可能受到语句之外执行的代码的影响。例如,如果在语句之后但在语句之前运行,则调用 print 语句将为您提供 ~6 秒,而不是 ~1 秒。t()
with
time.sleep(5)
with
print
t()
在某些情况下,可以通过在上下文管理器中插入 print 命令来避免这种情况,如下所示:
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)
# >>> "Time: 1.000 seconds"
sleep(5)
print(f'Time: {t():.3f} seconds')
# >>> "Time: 6.000 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)
# Now external delays will no longer have an effect:
sleep(5)
print(f'Time: {t():.3f} seconds')
# Output: "Time: 1.000 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)
# Output: "Time: 1.000 seconds"
sleep(5)
print(timer.time)
# Output: 1.000283900000009
sleep(5)
print(timer.readout)
# Output: "Time: 1.000 seconds"
这种方法提供了访问和利用经过时间的灵活性,既可以作为原始数据,也可以作为格式化字符串。
评分最高的答案中的问题也可以修复如下:
@contextmanager
def catchtime() -> float:
start = perf_counter()
end = start
yield lambda: end - start
end = perf_counter()
您可以通过以下方式进行操作:
import time
class Exectime:
def __enter__(self):
self.time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.time = time.time() - self.time
with Exectime() as ext:
<your code here in with statement>
print('execution time is:' +str(ext.time))
它将计算在“with”语句中处理代码所花费的时间。
评论
我喜欢这种方法,它使用简单,并允许上下文消息:
from time import perf_counter
from contextlib import ContextDecorator
class cmtimer(ContextDecorator):
def __init__(self, msg):
self.msg = msg
def __enter__(self):
self.time = perf_counter()
return self
def __exit__(self, type, value, traceback):
elapsed = perf_counter() - self.time
print(f'{self.msg} took {elapsed:.3f} seconds')
这样使用:
with cmtimer('Loading JSON'):
with open('data.json') as f:
results = json.load(f)
输出:
Loading JSON took 1.577 seconds
通过此实现,您可以在该过程中和之后的任何时间获得时间
from contextlib import contextmanager
from time import perf_counter
@contextmanager
def catchtime(task_name='It', verbose=True):
class timer:
def __init__(self):
self._t1 = None
self._t2 = None
def start(self):
self._t1 = perf_counter()
self._t2 = None
def stop(self):
self._t2 = perf_counter()
@property
def time(self):
return (self._t2 or perf_counter()) - self._t1
t = timer()
t.start()
try:
yield t
finally:
t.stop()
if verbose:
print(f'{task_name} took {t.time :.3f} seconds')
使用示例:
from time import sleep
############################
# 1. will print result
with catchtime('First task'):
sleep(1)
############################
# 2. will print result (without task name) and save result to t object
with catchtime() as t:
sleep(1)
t.time # operation time is saved here
############################
# 3. will not print anyhting but will save result to t object
with catchtime() as t:
sleep(1)
t.time # operation time is saved here
评论