测量时间的 Python 上下文管理器

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)

解决方案#1:上下文管理器方法(有修复)

t1该解决方案使用两个参考点和来捕获时间差t2。通过确保t2仅在上下文管理器退出时更新,即使在块之后存在延迟或操作,上下文内的经过时间也保持一致with

它的工作原理如下:

  • 进入阶段:进入上下文管理器时, 和 均使用当前时间戳进行初始化t1t2这确保了它们的差异最初为零。

  • 在上下文中:此阶段没有发生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块外的操作或延迟不会扭曲时间测量。与本页上其他评价最高的答案不同,此方法引入了一定程度的间接性,您可以在退出上下文管理器时显式捕获结束时间戳。此步骤有效地“冻结”结束时间,防止其在上下文之外更新。

解决方案#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)
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 秒

  • 还使用 perf_counter(),它比 time.clock() 更好。为什么这个不在标准库中? (2认同)
  • 虽然不完全是OP要求的,但我可以做类似`start = perf_counter(); 的事情 屈服; print(perf_counter()-start);` 对吗?我一直在寻找一个能够打印出执行其中内容所需的时间的 CM,这看起来很完美。 (2认同)
  • @水星是对的。这个答案是不正确的,因为“t()”生成的值也可能受到“with”语句之外的代码的影响。例如,如果您在“with”语句之后但在“print”语句之前调用 time.sleep(5),则“t()”将为您提供大约 6 秒,而不是 1 秒。 (2认同)

Bre*_*arn 9

您无法将时间分配给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 的值。


Are*_*ski 7

解决了(几乎)。结果变量是可强制转换的并且可以转换为浮点数(但不是浮点数本身)。

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)


Gre*_*reg 6

我喜欢这种方法,它使用简单并且允许显示上下文消息:

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)