Suggestion: Add a Stopwatch class to Python’s timeit module. The purpose of this class is to make it easy for Python users to measure time in a way that can be paused and unpaused. This class should expose the following methods:
unpause
pause
read
reset
and perhaps a toggle method. It should also expose a paused field, which indicates its current pause state.
Optionally, the class can receive the function that it should use to measure time (such as timeit.default_timer).
An example implementation is shown below:
class Stopwatch:
def __init__(self, time_func=timeit.default_timer):
self.time_func = time_func
self.reset()
def reset(self):
self.paused = True
self.accumulated = 0
def unpause(self):
if self.paused:
self.last_unpause = self.time_func()
self.paused = False
else:
raise RuntimeError("Stopwatch is already unpaused.")
def pause(self):
if not self.paused:
current = self.time_func()
self.accumulated += current - self.last_unpause
self.paused = True
else:
raise RuntimeError("Stopwatch is already paused.")
def read(self):
if self.paused:
return self.accumulated
else:
current = self.time_func()
return self.accumulated + current - self.last_unpause
def toggle(self):
if self.paused:
self.unpause()
else:
self.pause()
What do people think of this suggestion? I think it would be a convenient tool that can be used a wide range of situations.
I would find this very useful, but I’m not sure it belongs in timeit. Maybe in time? Or maybe it’s own small module?
From the docs:
timeit — Measure execution time of small code snippets
time — Time access and conversions
If people do agree this will be a useful in the stdlib, I am willing to help work on tests and docs for this, and on adding a context manager to the implementation. (Though this is small so it should be easy enough)
Just a tiny bikeshed, I would call it resume instead of unpause, or switch it to start/stop entirely.
I kind of like the idea of this because it can be used to time certain regions of code execution. I’ve had cases where we want to know how long (this part of) the code takes. I would say add contextmanager functionality to add time to the given stopwatch also to make that functionality easier too.
# inside my long function
with my_stopwatch.run():
# something that takes some time
# use my_stopwatch again later to add more (or reset) then use .total later on.
Could even have each run be a ‘lap’ and then give access to each lap to get statistics like avg, etc.
Besides that, I would also like a way to get a timedelta object instead of raw seconds.
I have been redirected here after my proposal was closed here (completely related) :
The class I proposed had a different API for not so different usage :
# import time
class Timer:
def __init__(self, text:str="", prompt=True, r=2):
self.text = text
self.prompt = prompt
self.r = r
def __enter__(self):
self.tic = time.time()
if self.prompt :
print(self.text, end=' ... ', flush=True)
return self.__call__
def __exit__(self ,type, value, traceback):
self.toc = time.time()
if self.prompt:
print('Done in ', str(round(self.toc-self.tic, self.r)), ' s.', flush=True)
def __call__(self):
return self.toc-self.tic
# display computation time and retrieve it
with Timer('computing things') as t:
time.sleep(1) # Do computations here
exec_time = t()
The use case I proposed allows to display execution time just by adding one line (and indenting). The time can be retrieved after the context manager is closed just by calling it with no argument.
The tool could be very versatile and helps ease the coding of time measures of code execution, plus being robust to exceptions leveraging the context manager protocol.
The API can be mixed for multiple usages, including Pythonista’s ones and mine.
The Stopwatch / Timer could be silent by default and verbose if a string is inputted as the argument text, thus displaying the text, three dots, and the time at the end, as in my original case.
I propose the following guidelines regarding the API :
I think it should start automatically when the context manager is __enter__ed, but we can have an optional argument autostart=True, if set to False, the start function can be called thereafter.
The pause, resume, toggle functions add to functionality.
The lap function would append measures times to self.intervals
The read function can be whether get_intervals or get_total functions.
The Stopwatch / Timer stops when __exit__ing the context manager, but the stop might add functionality. (then what would be the default behavior if start is called subsequently ?)
The __call__ method could return intervals as a list, maybe as a float if only one interval was measured.
A isrunning boolean property might also be useful.
The tool could provide a versatility of usages from a simple way of displaying execution time (with the fewest additional code possible) to more complex ways to perform multiple and sequential time measurements.
I want to add that I was not considering multiple time measurements, or pausing, because it can also be done by calling multiple Timers instances sequently, but additional functionality is good.
One issue to me is that I can not use this for inter-process timings.
Initially I have replaced this in my code, but then realised that this is not JSON serialisable as I stored the Stopwatch instance in my backend, which is serialised to json.
It isn’t an issue say for timeit.Timer, because its scope is limited to single process.
However, in this case, I think it would be more appropriate to have couple of utility functions that act on a simple state object.
I know you can just advise to use pickle, but I don’t think such a simple functionality should not depend on complex object serialisation. E.g. Say backend for state is some database, it would be both simpler and more convenient to be able to see state values without needing to deserialise.
The Timer was indeed made with flexibility in mind, even when multiprocessing computations that could raise an exception, Timer is cast within a context manager and a time value is always retrievable. For most complex coding that requires to measure different time (then add them up, subtract them, etc…) the approach taken was more like calling a Timer within a context manager each time, so that the post processing on time values is expressly made later. This was sufficient for my usage.
Yet more methods should improve the functionality of the class, or, as you say @dg-pb , Stopwatch could actually encapsulate Timer instances on an upper-level, eventually allowing parallel timings. We need a proper API description for this first.