Decorator Arguments
Here are some resources for more information about topics covered in this lesson:
00:00 In the previous lesson, I showed you how to use closures that wrap data functions as decorators. In this lesson, I’ll show you how to pass parameters to decorators. Spoiler alert, you’re going to add inner functions to your inner functions.
00:14 Decorators are closure generators and closure generators are just functions, so of course you can parameterize decorators. The syntax is pretty much the same as calling any other function.
00:26 You just pass the arguments to the decorator.
00:31 Back to the REPL, let’s look at some code.
00:34
Here’s my new decorator called who_says()
. It takes an argument, which will be a name. In the continued tradition of just adding another layer of inner functions, you can get your decorators to take parameters by using an inner function.
00:49
As a decorator is a closure generator that takes a data function, the first inner function is exactly that. Inside of that is the @wraps
decorator on the wrapper()
function like I showed you in the previous lesson. The logic in the wrapper checks if the name
argument for the decorator is 'simon'
and if it is, it runs the decorated function. If it isn’t, it raises an error.
01:11 Let’s see this in action. I’ll import the decorator,
01:18 define a decorated function,
01:29
And because Simon says, it was run! 8
is enough. We’re all good. Since the game is Simon Says, let’s see what happens if somebody else says.
01:44 I don’t know who Bob is, but evidently he doesn’t have the authority to do subtraction. I do a lot of Django programming. In Django, when you type in a URL in the browser, it gets mapped to a function called a view.
01:59
The function’s job is to return HTML, which then gets displayed. If the call to the server is a POST
, parameters need to be passed along to the view so that it can get the POST
data.
02:10
A common chunk of code at the beginning of that kind of view is to check that the POST
data is present. I have an open-source Django utility library called Awl that contains some decorators. There’s links in the notes below if you’re curious.
02:24
One of the decorators is called @json-post-required
. As you might guess, it checks for JSON data in the POST
. If it isn’t there, it sends back a 404.
02:34
This decorator is parameterized so that you can specify what POST
variable contains the JSON dictionary. With a decorator like this in place, all I have to do is decorate my view, and ten or so lines of validation code doesn’t have to be written over and over again.
02:50
It also acts as a nice piece of documentation. Right at the declaration line of the view, I can see that it takes JSON data and it requires a POST
. Decorators are a pretty powerful tool, and a common pattern in a lot of the web frameworks like Django and Flask for permission mechanisms like this one I just described.
03:10 You’ve now seen decorators with and without arguments. Decorators with arguments require one more layer of inner functions than those without. This would imply that you’re in an either/or situation—you either need them or don’t need them. Well, with a little creative work, you can actually create a decorator that supports argument lists or argument formats. Let me show you how.
03:37 You ready for this? It takes a little bit of mental gymnastics. In the previous example, I showed a decorator that took an argument. It used an inner function that was like the decorators you’ve seen before.
03:48
The challenge here is trying to figure out which mode you want to be in. Is your decorator taking parameters, or isn’t it? On line 4, I define a closure function generator called gift()
.
04:00
It takes an argument that I’ve called method_or_options
. If you decorate a function with @gift()
and give @gift()
parameters, method_or_options
will be those parameters. If, on the other hand, you decorate something with @gift
and don’t give it parameters, then method_or_options
will contain the function being wrapped.
04:21
And that’s the key: using the content of method_or_options
to decide which way to behave. On line 7, I check whether method_or_options
is not callable.
04:33
If it is callable, then gift()
took a function, which means it didn’t have any arguments. In this case, I’ll use the default value for fields
, which is an empty list.
04:43
If method_or_options
wasn’t callable, then it was a parameter that was passed in. In this case, I’ll point the fields
variable at those passed-in options.
04:54
This is why I called method_or_options
what I did. It either contains a method being wrapped or options for the @gift
decorator. Lines 6 through 8 figure out which is the case and set the fields
variable accordingly. Lines 10 and 11 put you back on familiar territory—the usual wrapper closure. In lines 12 through 15, I use the same if callable()
trick to print out a message that tells you how the decorator was invoked. Lines 17 and 18 are the usual return of the closure.
05:28 Here’s the last part you need to get this to work. Let me just scroll down.
05:33
Skip lines 20 and 21 for a moment. Go straight to line 23. This is what you’re used to—returning the inner function as a closure, the thing that does the work of the decorator. Now consider for a second what happens if there are no parameters to @gift
. In this case, there’s an extra layer of inner functions. You need to short circuit this inner layer. You can’t just return the closure, you have to invoke the closure. Line 20 detects that a function was wrapped and line 21 is the invocation of the closure, or in other words, doing what a decorator without parameters would do by default.
06:11 Think about this again for a second: a decorator is a closure around a data function. Our decorator with optional parameters either uses an inner function to get at those parameters or, if there were no parameters, instantiates the closure inside of the decorator and returns it.
06:29 Don’t feel bad if your brain just melted. Most of the time when I use this trick, I just look up how I did it last time. The first time I came across code like this, I had no idea what it was doing or why. But it worked!
06:41 Let’s look at it in practice. First, I’ll import it in.
06:47 Now I’ll write a decorator with no arguments,
06:55
run it, and the decorator reported that gift()
didn’t have any options. Let me do it again, this time passing in an option to the decorator.
07:17
The decorator reports that it saw an option with 'ribbon'
in it. You can also call it without any params but with a calling function syntax.
07:34
This time, you get the default value of fields
, which is an empty list. If you want an extra little challenge, see if you can modify gift()
so that it can be called not only without options but also with any number of options. The code as it stands only allows a single argument at the moment.
07:53 In the final lesson, I’ll wrap things up, summarizing everything you’ve learned.
Become a Member to join the conversation.