The Tecnoprom Core

Advanced and not-so-advanced technology

See my new website: DSSTI

Python Decorators

One of the ways to do advanced programming in Python is using decorators.

What are the decorators?

Decorators are functions that transform functions into other functions. They are usually used as annotations on functions that modify the behaviour of the function in curious ways. Since decorators modify functions, and you can apply several of them, they are a way to apply some functional programming in Python

Basic decorators

Let's start with a simple decorator. This decorator will store the result of a function, and return it immediately if called repeately, instead of calculating it again and again:

def memoize(wrapped_fn):
    def fn():
        if not fn._result_available:
            fn._result_available = True
            fn._result = wrapped_fn()
        return fn._result
    fn._result_available = False
    return fn


@memoize
def fn():
    print "Getting data"
    return 5

for i in range(10):
    print fn()

Decorators can have parameters, to configure them in a way. For example, here is a decorator that retries a failing function several times. It can be used to fetch resources from the Internet, even when the connection is unreliable:

class retries(object):
    def __init__(self, nretries=3):
        self.nretries = nretries

    def __call__(self, wrapped_fn):
        def fn(*args, **kwargs):
            for i in range(1, self.nretries):
                try:
                    return wrapped_fn(*args, **kwargs)
                except Exception:
                    print "{} failed {} times, retrying...".format(wrapped_fn, i)
            return wrapped_fn(*args, **kwargs)
        return fn

@retries(nretries=5)
def fn():
    raise Exception("Spam")

fn()

Decorators can be used for debugging and tracing what happens with your code. You can use them to enforce contracts, check parameters and return types. For example, here is a decorator that prints the returning value of a function:

def print_return(wrapped_fn):
    def fn(*args, **kwargs):
        res = wrapped_fn(*args, **kwargs)
        print "{} returned {}".format(wrapped_fn, res)
        return res
    return fn

@print_return
def fn():
    return 5

fn()

You can apply several decorators to a function. For example, here are a couple of silly decorators that apply HTML formatting to a function:

class HTMLTag(object):
    def __init__(self, tag):
        self.tag = tag
        self.open_tag = "<{}>".format(self.tag)
        self.close_tag = "</{}>".format(self.tag)

    def __call__(self, wrapped_fn):
        def fn(*args, **kwargs):
            return self.open_tag + wrapped_fn(*args, **kwargs) + self.close_tag
        return fn

@HTMLTag("h1")
@HTMLTag("i")
def title():
    return "This is a title"

print title()

Decorators are a way to enhace functions with common traits, and a very interesting feature in Python. Although creating them is a bit tricky, a good decorator should be easily reused.

What's next?

We should jump from simple examples, to really generic decorators, in order to increase the reusability of the code. In a future post we will have a look at some functional decorators.