🎓 A1Learner
🔍

Python Decorators

A decorator is a function that takes a function and returns a wrapped function.

The same lines repeat

Each function below prints a line before and after its own line.

def greet():
    print("Before")
    print("Hello!")
    print("After")

def pack_bag():
    print("Before")
    print("Bag packed")
    print("After")

greet()
pack_bag()

Store a function in a variable

The name shout holds the function itself. The variable say can call it.

def shout(text):
    return text.upper()
say = shout
print(say("hello"))

Pass a function as an argument

Another function can receive shout as a value. It can then call the function it received.

def twice(func, value):
    return func(value) + func(value)
def shout(text):
    return text.upper()
print(twice(shout, "go"))

Write a wrapper by hand

A wrapper is an inner function around the original. The decorator returns that wrapper.

def announce(func):
    def wrapper():
        print("Before")
        func()
        print("After")
    return wrapper

Apply the wrapper by hand

Assign the returned wrapper back to the name. Calls to the name now run the wrapper.

def greet():
    print("Hello!")
greet = announce(greet)
greet()

Use the @ syntax

The @ line means the same assignment as by hand. Python applies it when the function is defined.

@announce
def greet():
    print("Hello!")
greet()

Keep the original name

Without help, the wrapper hides the original name. functools.wraps copies the name and docstring across.

from functools import wraps
def announce(func):
    @wraps(func)
    def wrapper():
        print("Before")
        func()
        print("After")
    return wrapper

Example

from functools import wraps


def announce(func):
    @wraps(func)
    def wrapper():
        print("Before " + func.__name__)
        func()
        print("After " + func.__name__)
    return wrapper


@announce
def greet():
    """Greet the reader."""
    print("Hello!")


@announce
def pack_bag():
    """Pack a bag."""
    print("Bag packed")


greet()
pack_bag()
print(greet.__name__)
print(pack_bag.__doc__)

Key points

  • Function value: a function can be stored, passed, and returned
  • Decorator: a function that takes a function and returns a wrapped function
  • Wrapper: the new function that runs extra code around the original call
  • Use @name above a def line to apply a decorator at definition time
  • Use functools.wraps on the wrapper to keep the original name and docstring

In this example, announce takes a function and returns a wrapper. The wraps line keeps each original name and docstring. The @announce line replaces greet with its wrapper. It also replaces pack_bag with its wrapper. Each call prints a Before line first. The original function then runs. An After line prints last. The final prints prove the names survived decoration. One print also shows the saved docstring.

Note

After decoration, the name points to the wrapper.

That is why preserving its name matters.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Python Playground

Exercise

What does @announce above def greet(): mean?