This guide will show you how to create a Python function decorator with a few easy steps, to help you separate different concerns in code. This guide does not aim to provide reference material regarding decorators, but rather explain the basics and provide a starting point for implementing your own decorator.
What is a decorator?
A Python decorator is similar to a Java annotation if you are familiar with the JVM language, where you decorate or “annotate” a function prefixing the decorator’s name with @
. For example, the decorator below publishes the function hello()
on the url /
on a Flask application:
@app.route('/') def hello() -> str: return 'Hello world from Flask!'
More broadly speaking, a decorator is a structural design pattern that lets you add behaviour to an object, by wrapping its decorated function in a new function that has the same signature.
Creating a Python function decorator
In Python, a function decorator should follow these rules:
- A decorator is a function;
- A decorator takes the decorated function as an argument;
- A decorator returns a new function;
- A decorator maintains the decorated function’s signature.
Therefore, we can use the Python decorator template below as a starting point:
from functools import wraps def decorator_name(func): @wraps(func) def wrapper(*args, **kwargs): # 1. Code to execute BEFORE calling the decorated function # 2. Call the decorated function as required, returning if needed # return func(*args, **kwargs) # 3. Code to execute INSTEAD of calling the decorated function. return wrapper
Let’s now turn this decorator into something more useful. The decorator below checks whether a session
contains the the key logged_in
, if it does, the decorated function executes, otherwise, it returns a message saying the caller is not logged in:
from functools import wraps from flask import session def authenticated(func): @wraps(func) def wrapper(*args, **kwargs): if 'logged_in' in session: return func(*args, **kwargs) # executes the decorated function return 'Not logged in' return wrapper
Now, we can apply this new decorator to the hello()
function we saw at the beginning:
@app.route('/') @authenticated def hello() -> str: return 'Hello world from Flask!'