Undestanding decorators in Python Python 04.10.2013

Decorators dynamically alter the functionality of a function, method or class without having to directly use subclasses. This is ideal when you need to extend the functionality of functions that you don't want to modify.

Essentially, decorators work as wrappers, modifying the behavior of the code before and after a target function execution, without the need to modify the function itself, augmenting the original functionality, thus decorating it.

Decorators provide a simple syntax for calling higher-order functions.

The base idea is Functions can return other functions. In Python, functions are first-class objects. This means that functions can be passed around, and used as arguments, just like any other value

def compose_salute():
    def get_message():
        return "Hello there!"

    return get_message

greet = compose_salute()
print greet()

Simple decorator

from functools import wraps

def compose_salute(name):
   return "Hello {0}!".format(name)

def make_paragraph(func):
   @wraps(func) 
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

format_salute = make_paragraph(compose_salute)

print make_paragraph("John")

There is a neat shortcut for more concise set of statements, which is to mention the name of the decorating function before the function to be decorated. The name of the decorator should be perpended with an @ symbol.

from functools import wraps

def make_paragraph(func):
   @wraps(func) 
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

@make_paragraph
def compose_salute(name):
   return "Hello {0}!".format(name)

print compose_salute("John")

A function that takes another function as an argument, generates a new function, augmenting the work of the original function, and returning the generated function so we can use it anywhere.

Imagine that we wanna decorate text with different tag. We should change our decorator for accepting argument with tag name.

def tag(tag_name):
    def tag_decorator(func):
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tag_decorator

@tag("b")
def compose_salute(name):
   return "Hello {0}!".format(name)

print compose_salute("John")

Decorators expect to receive a function as an argument, that is why we will have to build a function that takes those extra arguments and generate our decorator on the fly.

Useful links