← All advanced lessons

Advanced Python / A06+

Decorator Notes

Supplemental decorator examples, explanations, and linked reference material.

Advanced Python - Decorators

Article: HERE

def my_decorator(func):

def wrapper():

print("Something is happening before the function is called.")

func()

print("Something is happening after the function is called.")

return wrapper

def say_whee():

print("Whee!")

say_whee = my_decorator(say_whee)

Can you guess what happens when you call say_whee()? Try it:

>>>

>>> say_whee()

Something is happening before the function is called.

Whee!

Something is happening after the function is called.

To understand what’s going on here, look back at the previous examples. We are literally just applying everything you have learned so far.

The so-called decoration happens at the following line:

say_whee = my_decorator(say_whee)

In effect, the name say_whee now points to the wrapper() inner function. Remember that you return wrapper as a function when you call my_decorator(say_whee):

>>>

>>> say_whee

<function my_decorator.<locals>.wrapper at 0x7f3c5dfd42f0>

However, wrapper() has a reference to the original say_whee() as func, and calls that function between the two calls to print().

Put simply: decorators wrap a function, modifying its behavior.

Python allows you to use decorators in a simpler way with the @ symbol, sometimes called the “pie” syntax. The following example does the exact same thing as the first decorator example:

def my_decorator(func):

def wrapper():

print("Something is happening before the function is called.")

func()

print("Something is happening after the function is called.")

return wrapper

@my_decorator

def say_whee():

print("Whee!")

So, @my_decorator is just an easier way of saying say_whee = my_decorator(say_whee). It’s how you apply a decorator to a function.

Let’s move the decorator to its own module that can be used in many other functions.

Create a file called decorators.py with the following content:

def do_twice(func):

def wrapper_do_twice():

func()

func()

return wrapper_do_twice

Note: You can name your inner function whatever you want, and a generic name like wrapper() is usually okay. You’ll see a lot of decorators in this article. To keep them apart, we’ll name the inner function with the same name as the decorator but with a wrapper_ prefix.

You can now use this new decorator in other files by doing a regular import:

from decorators import do_twice

@do_twice

def say_whee():

print("Whee!")

When you run this example, you should see that the original say_whee() is executed twice:

>>>

>>> say_whee()

Whee!

Whee!

Decorating Functions With Arguments

The solution is to use *args and **kwargs in the inner wrapper function. Then it will accept an arbitrary number of positional and keyword arguments. Rewrite decorators.py as follows:

def do_twice(func):

def wrapper_do_twice(*args, **kwargs):

func(*args, **kwargs)

func(*args, **kwargs)

return wrapper_do_twice

The wrapper_do_twice() inner function now accepts any number of arguments and passes them on to the function it decorates. Now both your say_whee() and greet() examples works:

>>>

>>> say_whee()

Whee!

Whee!

>>> greet("World")

Hello World

Hello World

Returning Values From Decorated Functions

To fix this, you need to make sure the wrapper function returns the return value of the decorated function. Change your decorators.py file:

def do_twice(func):

def wrapper_do_twice(*args, **kwargs):

func(*args, **kwargs)

return func(*args, **kwargs)

return wrapper_do_twice

The return value from the last execution of the function is returned:

>>>

>>> return_greeting("Adam")

Creating greeting

Creating greeting

'Hi Adam'

(only went up to Fancy Decorators in the article)