Add a Stopwatch class to the timeit module

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.

1 Like

Personally never needed this. Until now. Just simplified some code with it.

Are there any common use cases for this?
Standard library?
External packages?

Edited it a bit:

class Stopwatch:
    last = None

    def __init__(self, timer=default_timer):
        self.timer = timer
        self.intervals = list()

    @property
    def total(self):
        return sum(self.intervals)

    def reset(self):
        self.intervals.clear()
        self.last = None

    def run(self):
        if self.last is not None:
            raise RuntimeError('Stopwatch is already unpaused.')
        self.last = self.timer()

    def pause(self):
        if self.last is None:
            raise RuntimeError('Stopwatch is already paused.')
        self.intervals.append(self.timer() - self.last)
        self.last = None

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. :slight_smile:

yes, if you add

def __enter__(self):
   self.unpause()
   return self

def __exit__(self, _, __, ___):
   self.pause()

it should be able to work as a context manager, which I would find convenient.

1 Like

Stopwatch controls are typically start/stop/reset/lap.

Lap wouldn’t make sense in the Python context so start / stop / reset / (returns) and @property “elapsed” : datetime.timedelta

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.

Or accept optional backend which manages many stopwatches. All methods then would just have extra name argument.

Or a single watch, but that would add some complications for writeback backends, where backend is not a top level object.

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.

The following discussion is related to the current one: