Function/method decorator is something misunderstood by many developers: beginner but also more confirmed one.

Python like other language like Java have this concept of function decoration that can be used to modify behavior or inject content. You probably already see some, it is the line before a function declaration starting with an @. The most common one in python is probably @property. A built-in decorator that transform a method into a property (i.e. call an unbound method).

To understand how decorator works you must understand how python will apply it. If you have something like:

@format_result
def divide(x, y):
    ''' Divide x by x '''
    return float(x) / float(y)

When calling divide(2, 3) you will in fact call format_result(divide)(2, 3).

Let’s pretend that the goal of this decorator is to format the returned number into a string representing a float with a 2 digits precision. The implementation will be as follows:

def format_result(func):
    def wrapper(x, y):
        ''' Format a float to a 2 digits precision '''
        return '{:.2f}'.format(func(x, y))
    return wrapper

This is basically how to write a decorator in python. The first argument of the decorator is the decorated function/method. The decorator returns a wrapper that will accept some parameters (here the same as the function) and will be responsible to do whatever he want when called (here format the results).

The problem with this implementation is that if you try to get the name or even the docstring of the function will be get the wrapper one. To solves this problem you have to use the wraps decorator inside your decorator. It will correctly set all the decorated function attribute for you. Previous decorator will now looks like:

from functools import wraps
def format_result(func):
    @wraps(func)
    def wrapper(x, y):
        ''' Format a float to a 2 digits precision '''
        return '{:.2f}'.format(func(x, y))
    return wrapper

Maybe we now want to define per decorated method the format precision. Here is how parameters can be used with decorator:

def format_result(precision=2):
    def decorated(func):
        @wraps(func)
        def wrapper(x, y):
            return '%.{}f'.format(precision) % func(x, y)
        return wrapper
    return decorated

There is a little hack here in order to generate the string format and then format the result, but it is not the important thing here. In order to add a parameter to the decorator we have to wrap another time the function: the first level is just here to get the precision, it is in fact the second level (decorated) which is the decorator like previously. This make takes some time get it clear but once understood you have all the logic.

Now to set the prevision to 3 we will write:

@format_result(3)
def divide(x, y):
    return float(x) / float(y)

You now master python decorator, you should be able to easily rewrite the @property of the standard lib in python instead of C.

To conclude decorator can be very useful and can be used in a wide range of applications. One of my favorite are:

  • @cached_property from werkzeug that allows you to cache the return value of a property, very useful when fetching resource is resource consuming and value is static for the current object.
  • @login_required from django that check that a user is authenticated.
  • @route from flask that register a new route in your app.