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 wrapperApply 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 wrapperExample
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?