Python context manager that measures time
Solution 1:
Here is an example of using 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")
Output:
Execution time: 1.0014 secs
Solution 2:
You can't get that to assign your timing to t
. As described in the PEP, the variable you specify in the as
clause (if any) gets assigned the result of calling __enter__
, not __exit__
. In other words, t
is only assigned at the start of the with
block, not at the end.
What you could do is change your __exit__
so that instead of returning the value, it does self.t = time.clock() - self.t
. Then, after the with
block finishes, the t
attribute of the context manager will hold the elapsed time.
To make that work, you also want to return self
instead of 1
from __enter__
. Not sure what you were trying to achieve by using 1
.
So it looks like this:
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)
And a value pretty close to 1 is printed.
Solution 3:
Solved (almost). Resulting variable is coercible and convertible to a float (but not a float itself).
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
Solution 4:
The top rated answer can give the incorrect time
As noted already by @Mercury, the top answer by @Vlad Bezden, while slick, is technically incorrect since the value yielded by t()
is also potentially affected by code executed outside of the with
statement. For example, if you execute time.sleep(5)
after the with
statement but before the print
statement, then calling t()
in the print statement will give you ~6 sec, not ~1 sec.
This can be avoided by doing the print command inside the context manager as below:
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')
Notice how sleep(5) causes the incorrect time to be printed:
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"
A more robust solution (recommended)
This code is similar to the excellent answer given by @BrenBarn execept that it:
- Automatically prints the executed time as a formatted string (remove this by deleting the final
print(self.readout)
) - Saves the formatted string for later use (
self.readout
) - Saves the
float
result for later use (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)
Notice how the intermediate sleep(5)
commands no longer have an effect on the printed time.
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"