Quick Intro to Decorators
00:00 In the previous lesson, I showed you how to adjust the size of your LRU cache. In this lesson, I’m going into tangent land to give you a quick introduction to how to write decorators.
00:12
You used a decorator in the previous lessons, but how does it work? Well, decorators themselves are functions. They are functions that, along with the @
operator, combine to allow you to wrap another function. With the right syntax in place, you can write your own decorators. There are entire courses on it, but I’ll be giving you just a taste.
00:32 Decorators are usually used when you have pre- and/or post-conditions that you want to execute on a wrapped function. A common example for these is a decorator that times a function.
00:44 The pre-condition takes a timestamp, the wrapped function is then called, then the post-condition takes another timestamp and calculates the difference. Not being one to buck with tradition, let’s go do exactly that.
00:59
The how_long()
function that I’ve defined here in lines 4 through 13 is a decorator that times any function that it wraps. It’s built using an inner function to create a closure. Line 4 defines the function that is the decorator. It takes a single argument, which is the function that it is going to wrap. I’ve called that func()
.
01:21 The inner function is itself decorated. I’m not going to get into details here, but wrapping a function makes it appear differently on the stack and can make it hard to understand what failed if you get a traceback.
01:34
The @wraps
decorator from the functools
library fixes this problem, making the information shown when a decorated function fails closer to what you’d expect. Consider this part magic for now.
01:46
Abracadabra! Line 8 is where the wrapped function gets invoked. You invocate using the *args
and **kwargs
structures from the inner function.
01:57 Prior to this, on line 7, is the pre-condition. For a timing function, that pre-condition stores the current time. Lines 9 and 10 are the post-condition that figures out how long the wrapped function took then prints out the result. As your wrapped function may return something, you need to return it as well, or you’ll be breaking the wrapped function. Because all of this is happening inside of an inner function, that inner function needs to also be returned to wire the decorator up correctly. This isn’t the easiest concept to get your head around, unless you’ve done work in languages with closures before. Don’t worry if you don’t get it a hundred percent. For many years, I would just grab an example and edit it to my needs rather than fully comprehending how it worked.
02:45
It wasn’t until I started playing with function closures that the light bulb actually went on. Scroll down here. At the bottom of my script, you’ll find my handy pingala()
function, which I have wrapped using the new @how_long
decorator.
03:00
Then finally I invoke pingala()
and print the result. Let’s give this a whirl.
03:10
Did you expect all that? Remember that pingala()
is recursive. It calls itself. The decorator gets called each time. So instead of timing just the whole thing, you end up timing each execution of the function.
03:24
Notice that the last call took the longest time. It’ll be the sum total of all the preceding calls because of the recursion. And of course, that lonely 7
on the bottom is the printed results from line 26.
03:40 If all that wasn’t messy enough, you can actually give arguments to your decorators. This is done through yet another inner function. You’re going to your wrapper function in a wrapper function.
03:57
Let’s dissect this from the inside out. Look at the innermost function here, named wrapper()
, on line 7. Except for the fact that I’ve added a few more print()
statements, it’s the same as before. And like before, it’s decorated with the @wraps
decorator to take care of that pesky call stack mangling.
04:16
Peeling the onion one more layer, and you find a function called decorator()
. That takes an argument named func
. This is pretty much what the previous example called how_long()
did. The new bit is the extra layer on top.
04:32
This function wraps everything else. The arguments to this function will be the arguments that get passed to the decorator itself. In this case, I’m passing in an argument called message
, which I’ve included in the extra print()
statements on line 12.
04:48
Inner functions can access the context of their wrappers, so the message
argument is in scope at this innermost point. Let me scroll down just a bit.
05:00 The extra layer means that there is another wrapped function. So both the wrapper and then the decorator need to be returned. Scrolling down some more.
05:13
The code here is almost identical except how pingala()
is wrapped in how_longer()
, which takes a string as an argument, our message.
05:28 And there you go. Each invocation of the decorator prints out both the time and the message. It’s the same message every time, which isn’t too valuable, but it keeps the example simple.
05:39 You’ve now got a decorator that takes arguments.
05:45 That all was probably too fast. Decorators take a bit of mental processing. I know for the first twenty or so times I wrote them, I didn’t really get it, but you’ve seen the pattern and you can do as I did early on: copy and paste it and make it do what you want. Next up, I’ll add some time-based expiration to the LRU algorithm.
Become a Member to join the conversation.