1. Decorators that take arguments
Sometimes it would be nice to add arguments to our decorators. To do that, we need another level of function nesting.
2. run_three_times()
Let's consider this silly run_three_times() decorator. If you use it to decorate a function, it will run that function three times.
If we use it to decorate the print_sum() function and then run print_sum(3,5), it will print 8 three times.
3. run_n_times()
Let's think about what we would need to change if we wanted to write a run_n_times() decorator. We want to pass "n" as an argument, instead of hard-coding it in the decorator.
If we had some way to pass n into the decorator, we could decorate print_sum() so that it gets run three times and decorate print_hello() to run five times.
But a decorator is only supposed to take one argument - the function it is decorating. Also, when you use decorator syntax, you're not supposed to use the parentheses. So what gives?
4. A decorator factory
To make run_n_times() work, we have to turn it into a function that returns a decorator, rather than a function that is a decorator.
So let's start by redefining run_n_times() so that it takes n as an argument, instead of func.
Then, inside of run_n_times(), we'll define a new decorator function. This function takes "func" as an argument because it is the function that will be acting as our decorator.
We start our new decorator with a nested wrapper() function, as usual.
Now, since we are still inside the run_n_times() function, we have access to the n parameter that was passed to run_n_times(). We can use that to control how many times we repeat the loop that calls our decorated function.
As usual for any decorator, we return the new wrapper() function.
And, if run_n_times() returns the decorator() function we just defined, then we can use that return value as a decorator.
Notice how when we decorate print_sum() with run_n_times(), we use parentheses after @run_n_times. This indicates that we are actually calling run_n_times() and decorating print_sum() with the result of that function call. Since the return value from run_n_times() is a decorator function, we can use it to decorate print_sum().
5. Expanded code
This is a little bit confusing, so let me show you how this works without using decorator syntax. Like before, we have a function, run_n_times() that returns a decorator function when you call it.
If we call run_n_times() with the argument 3, it will return a decorator. In fact, it returns the decorator that we defined at the beginning of this lesson, run_three_times().
We could decorate print_sum() with this new decorator using decorator syntax.
Python makes it convenient to do both of those in a single step though. When we use decorator syntax, the thing that comes after the @ symbol must be a reference to a decorator function. We can use the name of a specific decorator, or we can call a function that returns a decorator.
6. Using run_n_times()
To prove to you that it works the way we expect here is print_sum() decorated with run_n_times(3). When we call print_sum() with the arguments 3 and 5, it prints 8 three times.
And we can just as easily decorate print_hello(), which prints a hello message, with run_n_times(5). When we call print_hello(), we get five hello messages, as expected.
7. Let's practice!
I tried to keep this example simple to explain the concept of decorators. In the next lesson, I'll show you some useful things you can do with decorators that take arguments like this. In the meantime, try these practice exercises to get more comfortable with how it works.