Yos Riady software craftsman 🌱

The Decorator pattern in Python

In object-oriented programming, the decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter pattern) is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.

In functional languages such as Scheme and Haskell, functions are first-class. This means that we can pass functions as arguments to other functions, assign functions to variables, and have them as return values just like any other primitive data types such as integers and strings.

In Python, functions are likewise treated as first-class citizens. In fact, the language provides syntactic sugar known as decorators which makes wrapping functions and function transformations even easier.

An example in Python:

def italicize(f):
    def wrapper():
        return '<i>' + f() + '</i>'
    return wrapper

def sayHi():
    return 'Hi there!'

italicHi = italicize(sayHi)
print italicHi() # prints '<i>Hi there!</i>'

In the code above, we have defined a function italicize which accepts a function sayHi, and returns a new function called wrapper which is then assigned to a variable italicHi.

Decorators are effectively function wrappers that are run when the python interpreter loads the function, and can modify what the function receives and returns.

We can rewrite the above code using the decorator pattern:

def italicize(f):
    def wrapper():
        return '<i>' + f() + '</i>'
    return wrapper

@italicize
def sayHi():
    return 'Hi there!'

print sayHi() # prints '<i>Hi there!</i>'

A decorator is a function that expects another function as a parameter. As you can see, the following two function definitions are equivalent:

def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...

We can also chain decorators, effectively nesting them:

def boldify(f):
    def wrapper():
        return '<b>' + f() + '</b>'
    return wrapper

def italicize(f):
    def wrapper():
        return '<i>' + f() + '</i>'
    return wrapper

@boldify
@italicize
def sayHi():
    return 'Hi there!'

print sayHi() # prints '<b><i>Hi there!</i></b>'

By this point, you may be wondering: what can I use decorators for? You can use them to extends several functions with the same code without rewriting it every time, for DRY’s (Don’t Repeat Yourself!) sake. A real-life decorator in a Django project (from the Django source):

def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    """
    Decorator for views that checks that the user is logged in, redirecting
    to the log-in page if necessary.
    """
    actual_decorator = user_passes_test(
        lambda u: u.is_authenticated(),
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

Say we have a bunch of views we want to only be accessible to a site’s admins. Instead of rewriting logic that checks for admin privileges for each of the views, we can simply define a decorator for admin access once, and decorate the relevant views with @admin_required. You can also parameterize your decorators:

@user_types_required(['ADMIN','MODERATOR'])
def func():
    ...

In short, decorators let you execute code before and after the function they decorate. Python’s decorator pattern is a great example of DRY. It helps you abstract away the repeating parts of your code into a function wrapper you can decorate other functions with.

Additional reading:

Author

Yos is a software craftsman based in Singapore.

📬 Subscribe to my newsletter

Get notified of my latest articles by providing your email below.


Going Serverless book

Interested to find out more about serverless? Going Serverless teaches you how to build scalable applications with the Serverless framework and AWS Lambda. You'll learn how to design, develop, test, deploy, and secure Serverless applications from planning to production.

Learn More →