Writing Decorators With Closures
00:00 One of the most common users of closures in Python is to create decorators. You may have used existing decorators already, but in this lesson, you will create your own decorator.
00:12
Let’s assume you want to create a decorator that limits any function you use it on to just three uses. So you can only use a decorated function three times, and the fourth time you get an error message or a warning. You can work in a script called decorators.py
.
00:30
You can create a function called limit_uses()
, which will be your decorator. And decorators accept functions as input. The function you pass to the decorator when you call it is the function you want to decorate, the function you want to give some extra features, some extra functionality.
00:49
And in limit_uses()
, the outer function, you can also define the inner function. Another common name for this inner function, especially in this context, is a wrapper.
00:59
So let’s call it a wrapper()
, and you can pass any *args
and **kwargs
to this wrapper()
.
01:08
And this wrapper()
function, the inner function, will be the function returned by limit_uses()
. So the decorator limit_uses()
will return this inner function wrapper()
.
01:19
This inner function will replace the original function, which you are referring to as func
in this decorator. Therefore, the wrapper()
function must call the original function with the same *args
and **kwargs
you pass to the wrapper()
.
01:37
And whatever value this function returns, you want wrapper()
to return it as well. This way, the wrapper()
function performs the same task as the original function.
01:48
However, you only want this function to be called three times, therefore, you must keep a counter. You can set the value of counter
to zero, and every time you call the inner function, you can increment this counter
.
02:04
However, you’ve seen this problem before. counter
is now a local variable of the inner function wrapper()
, however, you can declare it as nonlocal
to make sure the same counter
defined in the enclosing scope is used.
02:19
And since you’re incrementing counter
, you can now check whether counter
is less than or equal to three. And if it is, that’s when you call your original function and return any value it returns.
02:33
And if the counter
has gone beyond the limit, then you can print a message.
02:40
"You've run out of tries. Sorry!"
02:45
You’ve just defined a decorator, limit_uses()
as a decorator that accepts a function and will return another function that performs the same task as the original one, but has some extra logic.
02:58 In this case, you’re checking whether you’ve already called this function three times, and if you have, you print out a message instead of running the function again.
03:06
In order to try this decorator, you can create another function, let’s call it greet_person()
. And this function is there just to test your decorator.
03:16
So you can simply return the string "{host
says hello to {guest}"
.
03:25
You may already be familiar with the @
notation for creating decorators. We’ll come to this in a bit. Let’s first perform this decoration without using the @
notation.
03:37
To decorate greet_person()
, you can reuse the name greet_person()
and make it equal to whatever the decorator limit_uses()
returns.
03:48
But limit_uses()
needs a function as an argument. So you can pass greet_person()
as the argument to limit_uses()
.
03:56
So what you’re doing is you’re passing the function greet_
person()
to limit_uses()
, but limit_uses()
returns another function.
04:04
This is what you called wrapper()
within the decorator, but you reuse the name greet_person()
for this new function. Therefore, the name greet_person()
has now been replaced by a new function, which performs the same task but has some extra logic.
04:29 Let’s first check that the function works.
04:34
I call the script decorators
and James says hello to Mary
.
04:43
Let’s now call the greet_person()
function a few more times.
05:05
So now you call the greet_person()
function four times, and when you run the script, the first three times you get the output, James says hello to Mary
, Keith says hello to Dan
, and Sarah says hello to Alfred
.
05:18
However, the fourth time you try to call the function, you don’t get the output of the host greeting the guest. Instead, you’re told that You've run out of tries. Sorry!
You also have None
because the function always returns something and therefore this function still returns None
.
05:37
So you’ve used the decorator limit_uses()
to limit greet_person()
to only three uses.
05:44
And let’s finish this lesson by replacing the line you’re using to decorate greet_person()
with the normal notation. So we can comment this line out, and this line does exactly the same as when you add @limit_uses
just before the function definition. The @
syntax you often see before function definitions performs exactly the same task as the line you have just commented out.
06:11 Let’s confirm this by running the code.
06:14
And once again you have James says hello to Mary
, Keith says hello to Dan
and Sarah says hello to Alfred
. But the fourth time you try to call greet_person()
, you’re told that You've run out of tries. Sorry!
06:27
So the inner function wrapper()
is a closure, which includes the variable counter
within its extended scope, and we can make this decorator even more flexible.
Become a Member to join the conversation.