Wrapping functions with decorators

LEARN TO CREATE AND USE PYTHON DECORATORS


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 @verbosedef 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 finishesdef 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.

1 view0 comments

Recent Posts

See All

Yarn

A NEW AND FAST PACKAGE MANAGER There is a new kid on the block, and long story short, he is quite AWESOME. We are talking about YARN, a new package manager that wants to solve a few problems that have

Quick SSH access

HOW TO NOT GET LOST IN THE FOREST OF SSH SERVERS YOU HAVE TO WORK WITH For 8vance, one of the more complex projects that we're working on here in Cluj, we have a growing infrastructure - about 15 serv