Loading video player…

Simple Decorators

This lesson introduces decorators, functions that take other functions and extend their behavior without explicitly modifying them. You’ll see what decorators are and how they work. The examples in the lesson will also show you how to decorate your own functions.

00:00 All right. Now that you’ve seen all about functions and you’ve seen how functions are just like any other object in Python, let me show you some decorators.

00:11 Here’s an example of a simple decorator. I’ll have you define a function named my_decorator(). This function will take an argument of a function.

00:24 The argument will be named func. Next, you will create another function without any arguments that’s simply a wrapper. In its statement, it’ll start off with printing "Something is happening before the function is called". That function that was passed in as the argument will then be called just below that, and then another print statement.

00:52 At the very end here, at the end of the wrapper() function, from my_decorator() you’ll return the wrapper. Again, you can return a function from a function.

01:05 Now, we need a function that’s going to be passed. I’ll have you make a function named say_whee(), in the French style—more in the playful style.

01:13 This function will print "Whee!". "Whee!", ha. Okay. You have defined the two functions—the one that’s going to decorate the other.

01:26 Well, how do you do that? You are going to reassign say_whee using the assignment operator here, using my_decorator(), passing the function say_whee into it.

01:39 This side gets evaluated first, so say_whee, the function, gets passed into my_decorator(), so it travels into here, and then it’s wrapped inside of these two print statements and gets called here.

01:56 That wrapper is returned. When it’s returned like that, it can be assigned—just like you did before. Ready? All right, press Return. Now what’s going to happen if you inspect it?

02:11 say_whee is a function that is a reference to my_decorator.<locals> and that wrapper function that say_whee has been set to. Okay. What if you call it?

02:27 say_whee used to simply just print out "Whee!". But now, instead of just printing that, it will print this statement first—Something is happening before the function is called., and run the function, and then Something is happening after the function is called. All this code wrapping around your internal function.

02:49 So, where does decoration happen? Right here. This passing of an existing function, and then reassigning it to itself—well, to the same name. So, simply, how do we define a decorator?

03:10 Decorators wrap a function

03:14 and allow you to put other functional code around it. You can put things in front of it, things after it—anywhere within this statement.

03:27 Let me have you go a little further here. For the next example, I’ll have you import datetime from the standard library: from datetime import datetime.

03:39 I’m going to have you do a little bit of timing functionality.

03:46 You’re going to create a decorator that can wrap around another function and tell it what time of day it is to determine if it should run or not. So in this case, you’re defining this decorator not_during_the_night() and passing it, as an argument, func.

04:01 Next, you’re defining your wrapper() function. This will have a conditional statement in it.

04:09 So, this is a fun little statement. datetime.now() is going to call the current time and then return just the .hour portion of that datetime.now(), and if this is between, in this case, equal to or greater than 7, and less than—in this case, military time—10:00 PM, call func() else: pass. Pass on that.

04:38 It’s nighttime, the neighbors are asleep. You don’t want to wake them up. Okay. You’ve completed the wrapper(), now you need to return that wrapper function.

04:54 Let’s recreate say_whee(), the function. Again, it prints a very ecstatic "Whee!", which shouldn’t happen at nighttime. Okay. If you were to call out say_whee(), it’ll scream Whee!, but if we wanted to protect our neighbors’ sanity and our relationship with them, we can decorate it and say, “This function should only be called during the day—not during the night.” And in this case, we pass our function say_whee into it, so say_whee()this function right here—is getting decorated by not_during_the_night(), wrapped around this code right here with this conditional statement, and being reassigned here.

05:44 So say_whee now is a function with a reference to wrapper inside not_during_the_night, and you can call it because it depends what time of day it is wherever you are right now, running this. In my case, it’s the daytime.

06:00 So we can have some fun. All right! There’s your first look at decorating. Let’s go a little bit further and make decorators a little more readable, a little easier to spot, with some syntactic sugar.

Avatar image for HdVos

HdVos on March 20, 2019

To me it feels uncomfortable to reuse “say_whee” here, because you loose the original function. I would feel more comfortable doing: say_whee2 = my_decorator(say_whee) Is it python convention to do it like in the video? say_whee = my_decorator(say_whee)

Of course it depends on the rest of your program, but why use a decorator when it is always used in the decorated way?

Avatar image for Geir Arne Hjelle

Geir Arne Hjelle RP Team on March 20, 2019

Hi HdVos,

As you’ll see in the next video, the conventional way of adding decorators in Python is by using the form @my_decorator like this:

@my_decorator
def say_whee():
    print("Whee!")

This is just “syntactic sugar” that is equivalent to the say_whee = my_decorator(say_whee) statement you’ve seen in this video. As you say, this may look a bit weird because you lose the (direct) reference to the original function. However, in most real world cases you are not really interested in the original function, which is why the @-notation works like that.

Instead, you want to use a decorator exactly because it adds some permanent behavior to your function. The main reason to use decorators is not that you want to turn on and off some behavior for a function, but rather that you want to add the same kind of behavior to many functions.

However, if you want to use decorators to choose a specific behavior, I think the easiest would be to not do the decoration (with either the @ form or reassignment like in this video). Then, you choose in your code whether you call

say_whee()

or

my_decorator(say_whee)()

Finally, a few words about the reference to the original function being lost: In later videos you’ll learn about @functools.wraps. It will be used in decorators like this:

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper():
        print("Before")
        func()
        print("After")
    return wrapper

This does several nice things, but one of them is that it keeps a reference to the original function:

>>> say_whee()
Before
Whee!
After

>>> say_whee.__wrapped__
<function say_whee at 0x7f61cafdebf8>

>>> say_whee.__wrapped__()
Whee!
Avatar image for Michael Doornbos

Michael Doornbos on Jan. 1, 2022

I know it’s a little cliche, but I had a rare “ahh, I’ve always used this but never really knew how it worked” moment. Excellent explanation.

Avatar image for Patrick

Patrick on Jan. 19, 2022

Sometimes I have a problem watching the videos because of the white background just something you should consider making the videos with a dark background really helps on my eyes

Become a Member to join the conversation.