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.
Debugging Code With Decorators
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: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.
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
AugustoVal on March 28, 2019
Hello,
Thank you for the tutorial. Quick question.
Do you need always the boiling script to run the others?
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.
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 :)
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
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’.
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?
Ian Christy on Jan. 28, 2021
My understanding is that !r adds quotes around your string.
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.
mo on Dec. 25, 2021
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.
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?
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.
Get a Python Cheat Sheet (PDF) and learn the basics of Python, like working with data types, dictionaries, lists, and Python functions:


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