Decorators in Python

What are Decorators

A decorator is a callable that takes another function as argument and returns a new function as output. It’s used in the following way:

@logthis
def perform_action(n):
....
....

Decorators come under Metaprogramming i.e changing program behaviour at runtime.

Example

Decorators are executed right after the decorated function is defined. That is usually at import time. The following decorator is used to find the execution time of a function

import time
from functools import wraps

def timeit(func):
  """
  Reports the execution time
  """
  @wraps(func)
  def wrapper(*args,**kwargs):
    start = time.time()
    result = func(*args,**kwargs)
    end = time.time()
    print(func__name__,end - start)
    return result
  
  return wrapper

Example of using this decorator

@timeit
def exponentiation(b,n):
  exp_iter(b, n, 1)
  

def exp_iter(b,counter,product):
  if counter == 0:
      print(product)
  else:
      counter = counter - 1
      product = b * product
      exp_iter(b,counter,product)

and the output will be

16
exponentiation 0.00020766258239746094

The use of wraps(func)in the above code is there to preserve function metadata.This will help us in getting function metadata like:

exponentiation.__name__
exponentiation.__doc__

Defining a decorator that takes arguments

You can also define decorators that can accept arguments.For example let us take a decorator that adds logging to a function and allows the user to specify the loggin details:

from functools import wraps
import logging

def logger(level,name=None,message=None):

    """
    A simple logger decorator that allows the 
    user to specify the logging details like 
    name and message
    """
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getlogger(logname)
        logmessage = message if message else func.__name__

        @wraps(func)
        def wrapper(*args,**kwargs):
            log.log(level,logmsg)
            return func(*args,**kwargs)
        
        return wrapper

    return decorate    

Usage:

@logged(logging.DEBUG)
def add(x,y):
    return x + y

Decorators are definately an useful functionality.There are some advance decorator concepts that I will write about next, these are:

  • Defining decorators inside classes
  • Defining decorators as classes