Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Debugging Code With Decorators

In this lesson, you’ll see how to create a decorator for debugging code. You’ll see how the @debug decorator prints out the arguments a function is called with as well as its return value every time the function is called.

00:00 Now that you’ve timed some functions, how about debugging them? This decorator’s going to name all the arguments that are passed in and out of your function. So, this is our last decorator, @timer.

00:14 Just above it, if you have the boilerplate there, go ahead and copy it. Then below timer(), paste that decorator. I’ll leave some extra space. Okay.

00:31 The decorator is going to be named debug(). It’ll take a function, create a docstring that says

00:43 """Print the function signature and return value""". You’re still decorating your wrapper function with the @functools.wraps().

00:52 The wrapper function for the decorator is going to be renamed wrapper_debug().

01:04 You’ll assign args_repr. So, this returns the canonical string representation of the object for a in args, so this comprehension is going to return all the arguments out of args, and this is going to create strings for each one.

01:22 You’ll do something very similar with the keyword arguments. Using an f-string.

01:39 So here—don’t forget the s in .items()it’s going to create an iterable out of kwargs—that dictionary—and return each key in value. Over here, the f-string is going to format them, saying each key is equal to the string representation of the value.

02:01 Next, you create a combined signature.

02:11 It’ll use this string method named .join() that will concatenate all of these strings together. It will put this in between all of them. So, what are you putting together?

02:20 args_repr and kwargs_repr.

02:35 Next, right before calling the function print(), using an f-string, calling the function’s name using the .__name__ method, and then this is a parentheses in between these two f-string expressions that the signature then is going to be embedded in. So calling this function using all of these arguments. Great.

02:57 Then, value will be set to the function with any *args and **kwargs coming in, and what happens after? Print another f-string,

03:11 using .__name__ and the repr() version of it, saying how it returned the values and the .__repr__ of all those—those repr() versions.

03:22 Next, you’re going return value, and last, you need to return the wrapper_debug. Looks good! Now save. Okay. Next up, open your examples module.

03:39 You are already importing from the decorators module everything, by using this asterisk (*). Now, create a new function, decorate it with @debug.

03:54 This function will be named make_greeting(). It will take one positional argument, name, and one keyword argument, age, which is set to None by default. Set up a conditional: if age is None: return this f-string with the expression name. else: return with an f-string, using both of these arguments, both age and name.

04:23 Okay. So, make_greeting() requires a name as an argument, and then also can take an age. Then, it will print either of these two strings based upon the conditional if statement there. Go ahead and save.

04:40 Now to use make_greeting(), you need to start your REPL, and from your examples module, import make_greeting. Great. It worked perfect.

04:54 Does it exist? Yep! There it is. Let’s try it out and see how debugging works. First time, say hello to "Benjamin". So here, the @debug decorator said that it’s calling the function make_greeting() with this argument, 'Benjamin'.

05:15 'make_greeting'—the function name—returned 'Howdy Benjamin!' And at the end here, there’s the string that was returned. It looks like the @debug decorator’s working well.

05:29 How about if you said hello to "Richard"? Set an age of 112.

05:39 So this time, it shows calling the function with 'Richard' as our positional argument and the age key-value argument. 'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!' And here, you see the f-string below.

05:52 This example might not seem immediately useful, since @debug the decorator is just repeating what you wrote, but I suggest you use it on a few more complex functions. And there’s another advanced way to use it.

06:05 It’s actually more powerful when you apply it to a small convenience function that you didn’t directly call yourself. Let me show you that as an example.

06:14 It’s going to calculate an approximation of the mathematical constant e. So, you need to go back into your examples.py module, and I need to import something else to do it. Not only import the decorators, but import also—from the standard library—import math.

06:38 Now down here…

06:46 we’re going to reassign math.factorial to be a decorated version of it. Notice that @debug is available, and debug() is going to call math.factorial.

06:57 This is kind of going back a little bit in time when we weren’t using the pretty syntactic sugar of the @ symbol. This is how we were using decorators before. You’d take a function and pass it as an argument to your decorator.

07:14 Now, you’re going to approximate e. By default, that’ll have a number of terms of 18.

07:27 So this is going to return the sum() of 1 divided by the factorial of n, for n in the range() of the terms that you’ve entered as an argument. That factorial() function will be called multiple times, depending on the range().

07:43 If you’re interested in learning a little more about the math that’s going on behind here, there’s a link to the Wikipedia article in the written version of the tutorial.

07:53 Okay. Let me save, go ahead and exit the REPL, and restart it.

08:02 And then import approximate_e. Great. That worked good. Let’s make sure it’s there. Looks good! What happens when you run it? You can set a different number of terms. Cool!

08:19 So each time that factorial() runs, you can see it calling it. It’s calling factorial() of 0, factorial() of 1, 2, 3, 4.

08:30 And you can see the values being returned each time. With the example you just ran, you get a decent approximate value for the true value of e. Another useful tool for debugging functions—besides showing arguments coming in and out—could be slowing down code, especially if you’re using web services. Let me show you that next.

Avatar image for Sagar

Sagar on March 28, 2019

Thanks for the awesome tutorial on Decorators. If I had to study decorators for an interview this would be my one stop shop for decorators starting right from the Beginner

Avatar image for Sagar

Sagar on March 28, 2019

Thanks for the awesome tutorial on Decorators. If I had to study decorators for an interview this would be my one stop shop as it goes right from BASICS to a bit ADVANCED on one topic. Unlike a few other tutorials like OOPS which finished at a BEGINNER stage. Would have appreciated it even more had it cover some more advanced topics like multiple inheritance and usage of super. As I mentioned earlier many of us would like these video courses to be a one stop shop for any topic that is newly introduced. Thanks for all the effort in putting this together. One small feedback , if we could select a default video resolution for an entire topic or series instead of choosing manually for each video/lecture

Avatar image for AugustoVal

AugustoVal on March 28, 2019

Hello,

Thank you for the tutorial. Quick question.

Do you need always the boiling script to run the others?

Avatar image for Chris Bailey

Chris Bailey RP Team on March 28, 2019

Hi AugustoVal! The ‘boilerplate’ part of the decorators module isn’t required for running or applying the decorators to your functions. It is in the tutorial and used to give you a template for copying and modifying to your needs. It is not called or referenced in the other code.

Avatar image for Dan Bader

Dan Bader RP Team on June 18, 2019

@Anonymous:

One small feedback , if we could select a default video resolution for an entire topic or series instead of choosing manually for each video/lecture

I just added that as a feature—you can set the default video quality in your account settings :)

Avatar image for Tumise

Tumise on Dec. 24, 2019

hey i just wanted to find out why, doing this ” sqrt = debug(sqrt) gave and error but “math.sqrt = debut(math.sqrt)” did not. the error message was SyntaxError: invalid syntax

Avatar image for Chris Bailey

Chris Bailey RP Team on Dec. 27, 2019

Hi Tumise, It’s hard to tell what your SyntaxError could have been from without a bit more context. If I were to guess why it worked properly in your second example, instead of the first. You imported the math module by typing in ‘import math’. In that case you would need to prefix all the ‘math’ functions/methods with ‘math. ’ to access them, such as ‘math.factorial’ or ‘math.sqrt’. If you were to import ‘sqrt’ by itself using ‘from math import sqrt’ you could then access it by typing only ‘sqrt’.

Avatar image for Ian Christy

Ian Christy on Jan. 28, 2021

@3:21 in the video, line 46 uses dunder !r while line 43 has no !r. Can you explain why this is so?

Avatar image for Ian Christy

Ian Christy on Jan. 28, 2021

My understanding is that !r adds quotes around your string.

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Jan. 29, 2021

@Ian Christy That would be the case. More specifically, however, adding the !r modifier enforces the repr() function to be called on an object to convert it to string before formatting.

This lesson was great for me. It connects multiple loose ends and gives the experience of closure. I will be playing around with this some more in the coming weeks. I always want slow speekers: Christopher speaks way too fast for me, but it is just about perfect at speed 0.75.

Thanks a lot for this one. Do not throw it away ever. It might me an older video, but I think it has great value.

Avatar image for Ivanhoe

Ivanhoe on Jan. 11, 2022

Hi Chris,

thanks for a great tutorial. I was wondering what the best way is to use decorators as a “conditional checking function” which enables us to separate what a function does and when it does that. For example (sorry for the silly example), say I have a function multiply(a,b): def multiply(a,b): return a*b

Now I want this function to return ab only if a>0 and b>0, if they’re both negative i want it to return “--=+” and if one of the two is negative I want it to return “watch out, one of your inputs is negative!”.

I could simply add these conditionals to the “multiply” function and be done, but lets say I want to use decorators for this. How could I do it?

Do you have some other tips on using decorators for conditionals?

Avatar image for Geir Arne Hjelle

Geir Arne Hjelle RP Team on Jan. 11, 2022

@Ivanhoe,

here is an example doing something similar to what you describe:

import functools
import itertools


def all_nonnegative(func):
    @functools.wraps(func)
    def _all_nonnegative(*args, **kwargs):
        if any((negative := v) < 0 for v in itertools.chain(args, kwargs.values())):
            raise ValueError(f"Expected only non-negative arguments, got {negative}")
        else:
            return func(*args, **kwargs)

    return _all_nonnegative


@all_nonnegative
def multiply(a, b):
    return a * b

Here’s an example of using it:

>>> multiply(3, 41)
123

>>> multiply(-3, 41)
Traceback (most recent call last):
    ...
ValueError: Expected only non-negative arguments, got -3

As you note, this may not be the most useful example but it shows one way to use decorators for validation.

In practice, you’re probably better off using things like Pydantic validators to ensure your data are as you expect them: pydantic-docs.helpmanual.io/usage/validators/

Become a Member to join the conversation.