Skip to main content

Decorators in Python

·296 words·2 mins
Ankur Rathore
Author
Ankur Rathore
Senior Systems Engineer pivoting to High-Performance Infrastructure. Building zero-allocation network drivers and cache-friendly data structures.

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