Here Be Dragons

So Easy, a Caveman Could Do It

A decorator is merely a function (or callable object) that accepts a function and returns a function. A very basic decorator can be defined like any other function:

def my_decorator(func):
    return func

Functionally, the following two snippets are the same:

def my_func():
def my_func():
my_func = my_decorator(my_func)

Notice that the function call is implied in the first case. The portion after the @ character is evaluated when the function is defined, and just has to resolve to a callable object. There is no magic involved here.

The Ol’ Bait an’ Switch

As you can see above, a decorator is a function that returns a function, but there’s no reason that it needs to return the function it was given, although it is polite to at least call that function at some point. Let’s try replacing the given function with one of our own:

def my_decorator(bait):
    def switch():
        print 'Mreow'
    return switch

As you can see, this can be rather confusing for the caller of bark(). Let’s make a more useful decorator:

def trace(func):
    def inner(*args, **kwargs):
        print 'Entering function ' + func.__name__
        return func(*args, **kwargs)
    return inner

Let’s see how nice this is!

>>> @trace
... def bark():
...     print 'Bark'
>>> bark()
Entering function: bark

Much nicer! Notice that our inner function takes any and all arguments and passes them into func. It doesn’t need to, but it does make it easier to pass the arguments into the original function. If you’re writing a decorator for a Django view, for example, you might use the signature inner(request, *args, **kwargs). Just don’t forget to pass request into func as well!

Shifty-Eyed Decorators

Decorators that take parameters tend to confuse people the most because it involves yet another level of nesting. It’s actually not complicated at all though. Remember, when you apply a decorator, you’re evaluating a statement that resolves to a callable. The trick to adding parameters is to create a function that you can call that returns the real decorator.

def trace(debug_level = 'WARNING'):
    def decorator(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner
    return decorator

In this example, trace isn’t a decorator; it’s a decorator generating function.

Decorators Objectified

If you really don’t like using triply-nested functions to define your decorators, you can use a callable object instead. That is, your decorator takes parameters when you create the object and Python uses the call method to decorate the function.

Here’s the trace decorator as a class:

class trace(object):
    def __init__(self, debug_level = 'WARNING'):
        self._debug_level = debug_level
    def __call__(self, func):
        self.func = func
        return self.inner
    def inner(self, *args, **kwargs):
        return self.func(*args, **kwargs)

This object is used identically to the closure decorator above, but instead of calling a function that returns a function, you instead call the constructor to create a new callable object of type trace.

Personally, I dislike these because of the extra steps required to manually save the parameters and because I like my decorators to start with lowercase letters and my classes to start with uppercase letters.

Function Copycats

Sometimes, a function contains useful metadata that will be used elsewhere. For instance, if you add trace to a function that has already been decorated, trace will instead print out the name of the decorator instead of the function. We can get around that by using functools.update_wrapper to copy over the metadata. This can look a bit odd to write, but luckily for us, there’s a convenience decorator version, functools.wraps!

Here is the final version of trace:

from functools import wraps
def trace(debug_level = 'WARNING'):
    def decorator(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner
    return decorator

Decorators with Class

Class decorators are decorators that you apply to a class instead of a function. You define them the same way that you define a function decorator, but you don’t usually replace them with a proxy class. In the case that you need to seriously modify a class, you may want to look into metaclasses, which allow you full access to the class before it is officially defined, including the __init__ function for the class instance, instead of just the object instance.

Melissa Nuño

Written by Melissa Nuño. You should follow her on Github.