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
此代码留下 t=1
而不是 clock() 调用之间的区别。如何解决这个问题?我需要一种从退出方法中分配新值的方法。
PEP 343 describes 更详细地介绍了 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
您无法将时间分配给 t
。如 PEP 中所述,您在 as
子句中指定的变量(如果有)将被分配调用 __enter__
而不是 __exit__
的结果。换句话说,t
仅在 with
块的 start 处赋值,而不是在末尾赋值。
你可以做的是改变你的 __exit__
,这样它就不会 return 值,而是 self.t = time.clock() - self.t
。然后,在 with
块完成后,上下文管理器的 t
属性将保存经过的时间。
要实现这一点,您还需要 return self
而不是 __enter__
中的 1
。不确定您试图通过使用 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 的值。
下面是一个使用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 秒
评分最高的答案给出的时间可能不正确
正如@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
# Note: print is included here to guarantee only time of code inside the CM is measured
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"
更健壮的解决方案(推荐)
此代码类似于@BrenBarn 给出的出色答案,只是它:
- 自动将执行时间打印为格式化字符串(通过删除最后的
print(self.readout)
来删除它)
- 保存格式化字符串供以后使用(
self.readout
)
- 保存
float
结果供以后使用 (self.time
)
from time import perf_counter
class catchtime:
def __enter__(self):
self.time = perf_counter()
return self
def __exit__(self, type, value, traceback):
self.time = perf_counter() - self.time
self.readout = f'Time: {self.time:.3f} seconds'
print(self.readout)
注意中间 sleep(5)
命令如何不再对打印时间产生影响。
from time import sleep
with catchtime() as t:
sleep(1)
# >>> "Time: 1.000 seconds"
sleep(5)
print(t.time)
# >>> 1.000283900000009
sleep(5)
print(t.readout)
# >>> "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
我正在努力制作一段代码,允许测量在 "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
此代码留下 t=1
而不是 clock() 调用之间的区别。如何解决这个问题?我需要一种从退出方法中分配新值的方法。
PEP 343 describes 更详细地介绍了 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
您无法将时间分配给 t
。如 PEP 中所述,您在 as
子句中指定的变量(如果有)将被分配调用 __enter__
而不是 __exit__
的结果。换句话说,t
仅在 with
块的 start 处赋值,而不是在末尾赋值。
你可以做的是改变你的 __exit__
,这样它就不会 return 值,而是 self.t = time.clock() - self.t
。然后,在 with
块完成后,上下文管理器的 t
属性将保存经过的时间。
要实现这一点,您还需要 return self
而不是 __enter__
中的 1
。不确定您试图通过使用 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 的值。
下面是一个使用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 秒
评分最高的答案给出的时间可能不正确
正如@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
# Note: print is included here to guarantee only time of code inside the CM is measured
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"
更健壮的解决方案(推荐)
此代码类似于@BrenBarn 给出的出色答案,只是它:
- 自动将执行时间打印为格式化字符串(通过删除最后的
print(self.readout)
来删除它) - 保存格式化字符串供以后使用(
self.readout
) - 保存
float
结果供以后使用 (self.time
)
from time import perf_counter
class catchtime:
def __enter__(self):
self.time = perf_counter()
return self
def __exit__(self, type, value, traceback):
self.time = perf_counter() - self.time
self.readout = f'Time: {self.time:.3f} seconds'
print(self.readout)
注意中间 sleep(5)
命令如何不再对打印时间产生影响。
from time import sleep
with catchtime() as t:
sleep(1)
# >>> "Time: 1.000 seconds"
sleep(5)
print(t.time)
# >>> 1.000283900000009
sleep(5)
print(t.readout)
# >>> "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