Python Decorators

by James Johnson

Python decorators are a way of wrapping a function or method with another function or method. Decorators use the @name syntax and are placed on the line before the def keyword.

Simple Python Decorators Example

Python decorators are functions that return a new function that wraps an existing function. This is done usually by declaring a function within a function and returning the nested function:

def add_one(fn):
    def wrapped(*args, **kwargs):
        res = fn(*args, **kwargs)
        return res +1
    return wrapped

@add_one
def add(num1, num2):
    return num1 + num2

print(add(1, 2)) # prints 4

Notice in the example above that since the wrapped function is contained within the add_one function, it will maintain a reference to the fn variable that add_one is called with when it’s used as a decorator.

To make it even clearer what is going on with decorators when we use the @name syntactic sugar, the example below performs the same thing as the example above without using the @name syntax:

def add_one(fn):
    def wrapped(*args, **kwargs):
        res = fn(*args, **kwargs)
        return res +1
    return wrapped

def add(num1, num2):
    return num1 + num2

add = add_one(add)
print(add(1, 2)) # prints 4

Notice how the original add function is replaced by the wrapped function that add_one returns. This is exactly what is happening when the @name decorator syntax is used.

Python Decorators Example 2

For a slightly more useful example, a decorator that logs all of the arguments passed to a function as well as the result of the function might look like this:

def fn_logger(fn):
    def wrapped(*args, **kwargs):
        print("- calling {} with:".format(fn.__name__))
        print("-   args: {}".format(args))
        print("-   kwargs: {}".format(kwargs))
        res = fn(*args, **kwargs)
        print("- result: {}".format(res))
        return res

    return wrapped

@fn_logger
def add(num1, num2, times=1):
    res = 0
    for x in xrange(times):
        res += num1 + num2
    return res

res = add(1, 2, times=4)
print("res of 1 and 2 added 4 times is {}".format(res))

Running the example above yields the output:

jelly$> python example.py
- calling add with:
-   args: (1, 2)
-   kwargs: {'times': 4}
- result: 12
res of 1 and 2 added 4 times is 12

Python Decorators on Class Methods

Python decorators work just as well with class methods. The only “gotcha” is that you have to remember the self first argument that is passed to all class methods:

def fn_logger(fn):
    def wrapped(*args, **kwargs):
        print("- calling {} with:".format(fn.__name__))
        print("-   args: {}".format(args))
        print("-   kwargs: {}".format(kwargs))
        res = fn(*args, **kwargs)
        print("- result: {}".format(res))
        return res

    return wrapped

class NumberAdder(object):
    @fn_logger
    def add(self, num1, num2, times=1):
        res = 0
        for x in xrange(times):
            res += num1 + num2
        return res

adder = NumberAdder()
res = adder.add(1, 2, times=4)
print("res of 1 and 2 added 4 times is {}".format(res))

Running the example above yields this output:

jelly$> python example.py
- calling add with:
-   args: (<__main__.NumberAdder object at 0x7f8c833e30d0>, 1, 2)
-   kwargs: {'times': 4}
- result: 12
res of 1 and 2 added 4 times is 12