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.