1

I was looking for a solution for timing code execution. Of course, I found all sorts of solutions mostly suggesting the use of timeit module - which is pretty cool - or just using the time module. Neither of those really provides what I'm after.

I wanted a simple solution that

  • I can quickly add to the code without changing the structure or logic
  • can cover a single line or code block
  • elegant and clean enough to even leave it in

I think I finally figured it out - using the context manager's dunder methods __enter__ and __exit__.

The idea is we capture the time when the context manager starts (__enter__), then just let the code run in the context-manager-block, finally, we capture the time when the context manager ends (__exit__) and do something with the result (print and/or return).

So here's the snippet:

import time


class ExecutionTimer:

    def __init__(self, message='Execution time', unit='s'):
        self.message = message
        self.unit = unit
        self.start_time = None
        self.end_time = None
        self.result = None

    def __enter__(self):
        self.start_time = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.end_time = time.time()
        self.result = self.end_time - self.start_time
        self.print_time()

    def print_time(self):
        elapsed_time = self.result
        if self.unit == 'ms':
            elapsed_time *= 1000  # convert to milliseconds
        elif self.unit == 'us':
            elapsed_time *= 1e6  # convert to microseconds
        elif self.unit == 'ns':
            elapsed_time *= 1e9  # convert to nanoseconds

        print(f"{self.message}: {elapsed_time}{self.unit}")


if __name__ == '__main__':
    
    start_time = time.time()
    time.sleep(1)
    end_time = time.time()
    print(f"Execution (s): {end_time - start_time}s")

    with ExecutionTimer(message='Execution (s)', unit='s'):
        time.sleep(1)

    with ExecutionTimer(message="Execution (ms)", unit='ms'):
        time.sleep(1)

    with ExecutionTimer(message="Execution (us)", unit='us'):
        time.sleep(1)

    with ExecutionTimer(message="Execution (ns)", unit='ns'):
        time.sleep(1)

    # or just capture the value
    with ExecutionTimer(message="Execution (ns)", unit='ns') as my_timer:
        time.sleep(1)

    # notice we are not in the context manager any more
    print(my_timer.result)
    print(my_timer.result)

and its output:

Execution (s): 1.0000789165496826s
Execution (s): 1.0000693798065186s
Execution (ms): 1000.067949295044ms
Execution (us): 1000072.0024108887us
Execution (ns): 1000071287.1551514ns
Execution (ns): 1000077962.8753662ns
1.0000779628753662
1.0000779628753662

Process finished with exit code 0

There's definitely some room for improvement. It can be integrated with logging to only emit messages to a certain log level OR it could use a "dry run" parameter to control the execution print method. Etc.

Feel free to use, test, and modify it as you please. It would be awesome if you could leave your constructive comments, ideas, and improvements so we can all learn from them.

Cheers!

5
  • 1
    If you want feedback on working code you should post on Code Review. Commented Nov 23, 2023 at 0:07
  • 2
    Note: It is allowed and encouraged to answer your own question on Stack Overflow. You could have posted this as a self-answered question. Although in this particular case this question is already asked here: stackoverflow.com/questions/33987060/… and you should have answered there if your answer is / was unique. Commented Nov 23, 2023 at 9:07
  • Thanks! I clearly missed that checkbox. It seems I can't change it after posting. I'll keep it in mind. Commented Nov 28, 2023 at 10:16
  • You can simply post an answer at any time. You don't have to enable the checkbox while posting the question. Commented Nov 28, 2023 at 10:26
  • 1
    Do you mean if I edit the question above to leave the only problem description in it and post the rest as an answer that makes the difference? Commented Nov 28, 2023 at 10:31

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.