Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please refer to our video player troubleshooting guide for assistance.

Timing Functions With Decorators

In this lesson, you’ll see how to use decorators to measure the time a function takes to execute and print the duration to the console.

00:00 For this first example, I’ll have you create a decorator that can time functions and how long it takes for them to run. To start this process, back in the editor, inside the module decorators.py, copy the boilerplate and paste it a little bit lower in the code.

00:23 Great. There is one more import that needs to happen for this decorator. Along with importing functools, import—from the standard library—time.

00:36 Now for your copied decorator, name it timer().

00:43 Just below it, using triple quotes (""") create the docstring…

00:52 and then end that also with triple quotes. So, timer() takes a function into it, this is its docstring. Then it’s decorated with @functools.wraps(). Instead of wrapper_decorator, it’s going to be the timer decorator, wrapper_timer().

01:08 It will accept *args and **kwargs.

01:13 And then something we’ll do here, you will assign a start_time by using the method from time perf_counter() and calling it. I’ll leave a note here that it’s the first step that’s new outside of renaming.

01:31 So, that will happen before. Then, value = func(*args, **kwargs)—that’ll stay the same. And then what’s going to happen after the function is called, is you will assign an end_time,

01:44 using that same perf_counter() method inside time. Once the end_time has been assigned, you can calculate a run_time by taking the end_time and subtracting the start_time.

02:00 And let’s print to the console that it’s finished running your function by using the .__name__ method and add the flag to make sure that it uses the repr() version of the name.

02:17 Close the curly brace. And then, inside that f-string, you also will say it ran in run_time—as a float, with four places of precision—seconds.

02:31 Close off the print statement. Then, it will return any values from here that were calculated when the function was run, and then you need to return wrapper_timer.

02:46 And save. Okay, so here it is: your decorator that will time functions, that you just built. You can remove this additional comment. Looks good! So, when the function is called, it’s passed into here, a start_time is calculated, the function runs. When the function has been called and is completed, an end_time is generated.

03:10 Then a run_time is calculated by subtracting the start out of the end time, and then to the console is printed a statement f"Finished", our function’s .__name__and that’s good that we have our functools—and then the run_time, and then the value is returned. Great! Okay. Again, make sure you have saved, then back in examples, change your import statement.

03:33 You’re not just importing the decorator @do_twice, but importing all the decorators that have been created by using an asterisk (*).

03:42 I’m going to save. Okay. Let me have you define a function that wastes a bit of time. Decorate it with @timer, and as you defined it, it’s going to be called waste_some_time().

03:54 It takes in an argument of number of times, num_times. And for _ in range(), depending on how many number of times you’ve set, it will create a sum…

04:09 of all the squares for i in the range up to 10000. Go ahead and save. So now, you have a new function, waste_some_time(), that’s decorated with your @timer.

04:24 I’ll leave it showing here in the background. Okay. Time to waste some time. How to do that? Go ahead and re-enter into your REPL. Mine’s bpython again. Yours could be just typing python or python3.

04:38 You need to import waste_some_time(). To do that, from examples, simply do that: import waste_some_time. Please make sure that you’re still in the decorators/ folder that has these two modules inside of it. Okay.

04:53 It looks like that imported properly, it’s showing up here in my REPL. There it is: a function and its memory location. Now, when it’s called with an argument of just the number 11 time.

05:06 It will run—and run very quickly, as you can see. So, from the start time to the end time, it took 0.0026 seconds. Great! Let’s try something a bit longer.

05:19 How about 999 times?

05:28 Okay. That took a bit longer, almost two and a half seconds. Decorators are pretty advanced. It’s good to practice visually stepping through all the stages of your decorator. This next example will help with that even more.

05:44 You’ll build a tool for debugging functions.

Alan Eng on May 7, 2019

Mind = blown. The real-world examples really solidify my knowledge! Thanks.

LJIN Lab on Aug. 10, 2019

I’ve been timing my functions like a freaking caveman. I love you man.

David on May 7, 2020

I can’t spot my mistake! :( On Section 3.2 Both Moduloes in the same directory…

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()  # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()  # 2
        run_time = end_time - start_time  # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer()
@timer
def waste_some_time(num_times):
    for _ in range(num_times):
        sum([i**2 for i in range(10000)])

Ctrl+S Open a fresh REPL

$ bpython
bpython version 0.19 on top of Python 3.7.5 /usr/bin/python3
>>> from examples import waste_some_time
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    from examples import waste_some_time
  File "/home/david/core/examples.py", line 9, in <module>
    @timer
  File "/home/david/core/decorators.py", line 33, in timer
    return wrapper_timer()
  File "/home/david/core/decorators.py", line 28, in wrapper_timer
    value = func(*args, **kwargs)
TypeError: waste_some_time() missing 1 required positional argument: 'num_times'
>>> 

Other examples so far worked perfectly fine… Thanks in advance :)

David on May 7, 2020

Found the mistake (A good hint that I need to sleep LOL): When defining the decorator, instead of return the function object, I returned the function calling. instead of:

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()  # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()  # 2
        run_time = end_time - start_time  # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer()

Do this:(last line)

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()  # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()  # 2
        run_time = end_time - start_time  # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer #  no "()" here, we need the object to 
                         #  be returned.

Pierre on June 20, 2020

Hi Christopher,

Thanks for the great tutorial. I never could wrap my head around the concept of decorators until this video series. This and the other course I just watched on generators were, alone, so well worth the cost of the subscription to Real Python. I particularly appreciate the pace. You seem to have found the sweet spot between too slow and too fast (at least for my level of Python literacy).

One question, I’ve never seen the phrasing for _ in range(n). Is the _ just a placeholder that can be used where you don’t need to reference what seems to amount to the index of the range?

Ricky White RP Team on June 20, 2020

That’s correct, Pierre. It’s often used in Python for variables that don’t need to be referenced again. If you find yourself using it inside of the for loop later on, you should refactor and give the variable a more semantic name.

sndselecta on Jan. 7, 2024

func.__name__!r Please explain the !r

Thx

Geir Arne Hjelle RP Team on Jan. 7, 2024

Hi sndselecta.

The !r specifies that the f-string should use repr() to format the name instead of str() which is the default. In practice, it adds quotes around the name.

You can learn more about repr() and str() at realpython.com/python-repr-vs-str/ We have more information about !r at realpython.com/python-f-strings/#using-an-objects-string-representations-in-f-strings

sndselecta on Jan. 7, 2024

Thank you Geir Arne Hjelle, quick question is it possible edit (crUd), a post?

Bartosz Zaczyński RP Team on Jan. 8, 2024

@sndselecta I’m afraid you can’t edit comments, unfortunately. I fixed your formatting if that’s what you were after.

Become a Member to join the conversation.