Wrapping functions with decorators
Learn to create and use Python decorators

Development Cluj-Napoca, Sept. 3, 2015, by Gabi
Estimated reading time:

If you just started using python or django you may have encoutered some functions that have an @ sign followed by a name just before their definition. In python world this is a syntax to declare that a function is wrapped inside another function (the later being called a decorator).

Small recap

To try understanding decorators let's remember some things about functions and variables in python:

  • a function is something that generates a value based on the values of its arguments.

  • functions are first-class objects

  • functions create a new scope

  • *args and **kwargs are available in function scope

  • variable resolution order is done via the LEGB* rule

  • variables have a lifetime

* LEGB rule reffers to Local, Enclosing(function), Global (module), Built-in (python) 

Also you should know that in python you can have nested functions, that means we can declare functions inside of functions. They are used mostly because they offer encapsulation and closures (causes the inner function to remember the state of its environment when called). 

 

Definition of a decorator

 

A proper definition of a decorator can be as following: a decorator is just a callable that takes a function(usually) as an argument and returns a function object(usually). Their main purpose is to add features to the original function. It helps reducing boilerplate code and also with separation of concerns. They enhance readability and maintainability and they are explicit.

Example:

def verbose(original_function):

   def new_function(*args, **kwargs):
       print("Entering", original_function.__name__)
       res = original_function(*args, **kwargs)
       print("Exiting ", original_function.__name__)

       return res

   return new_function

Same example as above but decorator is defined as class:

class verbose():

   def __init__(self, original_function):
       self.original_function = original_function

   def __call__(self, *args, **kwargs):
       print("Entering", self.original_function.__name__)
       res =self.original_function(*args, **kwargs)
       print("Exiting ", self.original_function.__name__)

       return res

 

How to invoke the decorator

 

There are 2 options to invoke a decorator:

  • Just by passing a function as input to the decorator function and get back the “enhanced” version of it

  • Using decoration syntax @ - is basically the same as the above, but it adds syntactic sugar
# first variant 

def add1(a, b):
    return a+b

verbose(add1)(4, 5)

# second variant 

@verbose
def add2(a, b):
    return a+b

# implies automatic call of the decorated function

add2(4, 5)

 

Passing arguments to a decorator

 

Since a decorator must accept a function as an argument, you cannot pass the arguments directly to the decorator. To solution is quite simple, just create a normal function that returns a decorator.

Here is a sample of decorator that adds execution time if it's specificed by the parameter:

def verbose(include_execution_time=False):

    def verbose(original_function):
        # make a new function that prints a message 
        # when original_function starts and finishes

        def new_function(*args, **kwargs):
            print "Entering", original_function.__name__
            t = time.clock() if include_execution_time else None
            original_function(*args, **kwargs)
            print "Exiting ", original_function.__name__,\
                  " (execution time: {0})".format(time.clock()-t) if t else ""

        return new_function    

    return verbose
 

 

Decorator types

 

Function aren't the only ones that can be decorated, here are other types of decorators:

  • Method decorators

    • @classmethod: the class of the object instance is implicitly passed as the first argument instead of self. This can be useful for having a different constructor (eg. date instances can be created with  datetime.date(year, month, day), but you can also use classmethod date.fromtimestamp(timestamp) )

    • @staticmethod: neither self (the object instance) nor cls (the class) is implicitly passed as the first argument (behave like plain functions)

  • Class decorators

  • Even a decorator for decorators (sounds like inception)
  • Chain decorators

Sample of method decorators:

class Example(object):
    name = "Example"

    @classmethod
    def say_my_name(cls):
        print "%s class method called" % cls.name
    
    @staticmethod
    def say_name(name):
        print "%s static method called" % name

# no need for an instance

Example.say_my_name()
Example.say_name('Other name')

Sample of class decorator:

def verbose(original_function):
    # make a new function that prints a message 
    # when original_function starts and finishes
    def new_function(*args, **kwargs):
        print "Entering ", original_function.__name__
        original_function(*args, **kwargs)
        print "Exiting ", original_function.__name__

    return new_function

def class_verbose(cls):
    "The class decorator example"
    class NewClass(cls):
        "This is the overwritten class"
        def __getattribute__(self, attr_name):
            obj = super(NewClass, self).__getattribute__(attr_name)
            if hasattr(obj, '__call__'):
                return verbose(obj)
            return obj
    return NewClass

@class_verbose
class A(object):
    def say_hi(self):
        print "Hi"
 

 

Where they can be useful?

 

There are many places where decorators are useful, here is a short list:

  • logging

  • benchmark

  • type checking

  • synchronization (two or more functions on a given lock.)

  • memoize (caches a function's return value)

  • django (authentication, view permission and caching, etc.)

  • many others

 

Stackoverflow reference

 

For deeper undestanding and other example you can visit the folowing link. You can also contact us for a more detailed discussion.
You can find other interesting articles on similar topics here.

 
Image of Gabi
About the author
I am too shy to write smth about myself.

17 Comments



Leave a Comment