00:00 In the previous lesson, I showed you how to use closures. In this lesson, I’m going to talk about a special use of closures called decorators. You’ve seen closures used to contain data and update data.
00:13 Everything in Python is an object, including the functions, which means they can be treated like data. In the top area here, I have a closure that takes a function as its data. “Dude, I heard you like functions!
00:25 How about an inner function that takes a reference to a function?” Like the turtles, it’s functions all the way down. Why would you do this craziness? Well, this mechanism allows you to create preconditions and postconditions for a function. That would be code that is automatically run before and/or after the wrapped function. Two common uses for this are logging—for example, logging a function was called and when it was returned—and timing, timing how long a function took. In the top area here, I’ve written a closure that takes a function. It records the start time, runs the function passed in, then checks the amount of time that has passed.
Don’t let this hurt your brain. This really isn’t any different from the closures you’ve seen before. The only change is that the data being passed in is a function reference instead of an integer. Line 4 defines the containing function that takes a reference to a function called
01:34 Line 6 grabs the current time and line 7 is the meat. This is where the data function contained in the closure is run. Whatever arguments are passed to the closure are used with the data function.
02:17 My first computer didn’t have a hard drive. Yes, I’m that old. The version of BASIC I was using didn’t have timers in the library, so if you wanted to wait some amount of time, you’d spin like this in a loop. You had to base your counter on your processor speed to turn loop iterations into seconds.
When someone tells you about the good old days, they’re either lying to you or misremembering. Tangents aside, let me call this function. Looping
10000 doesn’t take very long on a modern machine.
What I just demonstrated was the hard way to do things. The easier way is to use the decorator syntax. Remember, a decorator is simply a closure that takes a data function and executes it. Python uses the
@ symbol to denote the use of a decorator.
You just sprinkle the decorator over top and you’re good to go. Somewhere else in this code, you would have had to have written a closure-generating function called
decorator(), which returns a closure, but if you have, the
@ symbol makes it easier to wrap anything.
There’s less code to write. All right, I’m back in the REPL. In the top area, I have the exact same code as before. Nothing has changed in
fn_timer() (function timer). In the bottom area, I’m going to define the
count() function like I did before but this time, instead of using
fn_timer() as a closure generator, I’ll use it as a decorator. Let me just import the decorator,
And that’s it! This is the same as before, but this time you don’t have to track another variable for the function closure. You just use
count() directly, and it is already wrapped. Let me call it.
And there you go! One side effect to note here. Because the decorator’s replacing a closure that wraps a data function, the calling information associated with
count() will no longer be correct.
The docstring associated with
count is no longer what you’d expect. It’s the docstring associated with
time_wrapper, which in this case, doesn’t have one. All of this can be fixed with
This time, I’ve switched it up and written a little logging decorator. It’s a bad logging decorator as it only uses
print(), but it’s good enough for our purposes. On line 5, I use the
@wraps decorator to wrap the closure.
The name of the decorator has to be meaningful to other programmers, but the closure inside can just be generic. With the
@wraps decorator in place on the closure
wrapper(), the name and the docs problem goes away. Let me import it,
Become a Member to join the conversation.