Are*_*ski 8 python with-statement
我正在努力制作一段代码,允许测量在“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
Run Code Online (Sandbox Code Playgroud)
这段代码留下t=1而不是 clock() 调用之间的区别。如何解决这个问题?我需要一种从 exit 方法中分配新值的方法。
PEP 343更详细地描述了联系管理器的工作原理,但我不了解其中的大部分内容。
Jus*_*rty 39
正如@Mercury 所指出的,@Vlad Bezden 的另一个最佳答案虽然很圆滑,但在技术上是不正确的,因为产生的值t()也可能受到在语句之外执行的代码的影响with。例如,如果您time.sleep(5)在该with语句之后但在该print语句之前运行,则t()在 print 语句中调用将为您提供约 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')
Run Code Online (Sandbox Code Playgroud)
然而,即使进行了此修改,请注意稍后运行 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"
Run Code Online (Sandbox Code Playgroud)
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"
Run Code Online (Sandbox Code Playgroud)
使用这种方法,with块外的操作或延迟不会扭曲时间测量。与本页上其他评价最高的答案不同,此方法引入了一定程度的间接性,您可以在退出上下文管理器时显式捕获结束时间戳。此步骤有效地“冻结”结束时间,防止其在上下文之外更新。
这种方法建立在 @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)
Run Code Online (Sandbox Code Playgroud)
与解决方案 #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"
Run Code Online (Sandbox Code Playgroud)
这种方法提供了访问和利用经过时间的灵活性,无论是原始数据还是格式化字符串。
Vla*_*den 15
这是使用上下文管理器的示例
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")
Run Code Online (Sandbox Code Playgroud)
输出:
执行时间:1.0014 秒
您无法将时间分配给t. 如 PEP 中所述,您在as子句中指定的变量(如果有)被分配调用的结果__enter__,而不是__exit__。换句话说,t仅分配在开始所述的with块,而不是在端。
你可以做的是改变你的__exit__,而不是返回值,它确实self.t = time.clock() - self.t。然后,在with块完成后,t上下文管理器的属性将保存经过的时间。
为了使这项工作,您还希望 returnself而不是1from __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)
Run Code Online (Sandbox Code Playgroud)
并且打印出非常接近 1 的值。
解决了(几乎)。结果变量是可强制转换的并且可以转换为浮点数(但不是浮点数本身)。
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
Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法,它使用简单并且允许显示上下文消息:
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')
Run Code Online (Sandbox Code Playgroud)
这样使用它:
with cmtimer('Loading JSON'):
with open('data.json') as f:
results = json.load(f)
Run Code Online (Sandbox Code Playgroud)
输出:
Loading JSON took 1.577 seconds
Run Code Online (Sandbox Code Playgroud)