Introduction

Decorator takes a function as an argument, adds some functionality and returns it. Generally, decorators are used when we need to add some functionality to the existing code. 

Things need to know before getting started with decorators

We know that everything in python is object including function, method and class. Names that we define are just identifier bound to the objects.

def fun(text):
	return text
	
print(fun("hello world"))
f2 = fun
print(f2("hello world"))

Output

hello world
hello world

Here in both cases we got same output. Both fun and f2 refer to same function object.

 

In python, function can be passed to another function as an argument. Map, filter and reduce are examples of it.

Let's see an example
def first():
	print("hello i am from function first")
	
def dec(func):
	print("hello i am from dec function")
	func()
	print(func.__name__+ " is passed inside dec")
	
dec(first)	

Output

hello i am from dec function
hello i am from function first
first is passed inside dec

Here first function is passed to dec function. Inside dec function, first we print a message that says it is from dec function and we called a function that is passed to it which is actually first function. 

 

In python, function can be defined inside a function and can return it

Let's see an example

def Outsider():
	def inner():
		print("hello i am inner function")
	return inner
	
fun = Outsider()
print(fun())

Output

hello i am inner function

Inside Outsider function, another function inner is defined. The Outsider function returns whole inner function. And whenever the Outsider function is called the inner function is returned.

 

Getting started with decorator

Let's take an example in which a function performs a division but doesn't do exception handling. If we want to add exception handling part we do it by decorator

def div_decorator(func):
	def inner(a,b):
		try:
			return a/b
		except Exception as e:
			return  e
		return func(a,b)
	return inner

#original function
def div(a,b):
	return a/b
	
div = div_decorator(div)
print("first case: ", div(5,2))
print("second case: ", div(4,0))

Output

first case:  2.5
second case:  division by zero

Here, div function is decorated by div_decorator that adds the exception handling part. Inside decorator function, we define a new function named inner which do exception handling part and returns called function. Above format is not standard way to create decorator, here is how

def div_decorator(func):
	def inner(a,b):
		try:
			return a/b
		except Exception as e:
			return  e
		return func(a,b)
	return inner

@div_decorator
def div(a,b):
	return a/b	

print("first case: ", div(5,2))
print("second case: ", div(4,0))

Output

first case:  2.5
second case:  division by zero

The latter one is standard format to create a decorator.

 

Another simple decorator example
def decorator(func):
	def inner():
		result = func()
		return result.title()
	return inner

@decorator
def fun():
	return "hello world"
	
print(fun())

Output

Hello World

In this example, the decorator just change the string into title case.

 

Single decorator to multiple functions

def decorator(func):
	def inner(*args):
		ele = list(args)
		for i in ele[1:]:
			if i == 0:
				return "cannot divide by zero"
		return func(*args)
	return inner

@decorator
def division1(a,b):
	return a/b
	
@decorator	
def division2(a,b,c):
	return a/b/c
	
print("First try: ")
print(division1(3,5))
print(division2(3,3,1))
print("\n")

print("Second try: ")
print(division1(3,0))
print(division2(3,0,1))
print("\n")

print("Third try: ")
print(division1(0,5))
print(division2(0,3,1))

Output

First try:
0.6
1.0


Second try:
cannot divide by zero
cannot divide by zero


Third try:
0.0
0.0

Here, this decorator decorates multiple functions having different length of argument. We use *args so that decorator can handle any length of input argument. Two functions having number of argument 2 and 3 is decorated. Inner function of decorator takes argument and checks whether the elements are zero or not rather than first element. If condition matches it returns message else it returns passed function itself. 

 

Multiple decorator to single function

def decorator1(func):
	def inner():
		text = func()
		return text.title()
	return inner
	
def decorator2(func):
	def inner():
		text = func()
		return text.split(" ")
	return inner

@decorator2
@decorator1										
def fun():
	return "hello world"
	
print(fun())

Output

['Hello', 'World']

Here function named fun is decorated by two decorators. The first decorator conver string to title case and latter one splits by space. At first, decorator1 decorates fun function and then decorator2 decorates the function.

 

Class decorator

Instead of function as a decorator we can use class to decorate a function

class Decorator:
	def __init__(self, func):
		self.func = func
	
	def __call__(self, a, b):
		if b == 0:
			return "cannot divide by zero"
		else:
			return self.func(a,b)

@Decorator
def div(a,b):
	return a/b
	
print(div(10,2))
print(div(10,0))

Output

5.0
cannot divide by zero

Class can also be used to decorate a function. For this  __call__  method must be used because when user try  to create an object that acts as function, the class decorator must return object that acts as function.

 

Let's take another example.

Suppose there is a function that returns cube of a number. If user wants the function to return the cube of as many number passed to it with affecting the original function, can be done using decorator

class Decorator:
	def __init__(self, func):
		self.func = func
	
	def __call__(self, *args, **kwargs):
		return list(map(lambda x: x**3, args))
		
@Decorator
def cube_function(n):
	return n**3
	
print(cube_function(1,2,3,4))

Output

[1, 8, 27, 64]

Here, the original cube_function returns cube of single number. Then it is decorated by using class decorator to return cubes of multiple numbers.

 

Till now we're dealing with user defined decorator but there are some built in decorators such as @property, @classmethod and @staticmethod 

Property decorator

Let's take an example

class Employee:
	def __init__(self, name, address):
		self.name = name
		self.address = address
		
	def show(self):
		return self.name + " lives in " + self.address
		
e1 = Employee("Ram", "Okhaldhunga")

# accessing attributes of class
print("Name: ", e1.name)
print("Address: ", e1.address)

#access method of class as an attribute
print("\naccessing method as an attribute: ")
print(e1.show)

Output

Name:  Ram
Address:  Okhaldhunga

accessing method as an attribute:
<bound method Employee.show of <__main__.Employee object at 0xaee9aca0>>

Here we got weird output. It is because we're trying to access method of class as an attribute. If user want to access method as an attributeproperty decorates comes into picture

class Employee:
	def __init__(self, name, address):
		self.name = name
		self.address = address
		
	@property
	def show(self):
		return self.name + " lives in " + self.address
		
e1 = Employee("Ram", "Okhaldhunga")

# accessing attributes of class
print("Name: ", e1.name)
print("Address: ", e1.address)

#access method of class as an attribute
print("\naccessing method as an attribute: ")
print(e1.show)

Output

Name:  Ram
Address:  Okhaldhunga

accessing method as an attribute:
Ram lives in Okhaldhunga

Using property decorator method of a class can be accessed as an attribute.

 

Classmethod decorator

class Employee:
	count_employee = 0
	
	def __init__(self,name, id):
		self.name = name
		self.id = id
		Employee.count_employee += 1
		
	def show(self):
		return self.name + "'s id is " + str(self.id)
	
	@classmethod		
	def employeeNumber(cls):
		return cls.count_employee

#create instances of class				
e1 = Employee("Shiv", 45)
e2 = Employee("Rajesh", 34)
e3 = Employee("JJ Thompson", 12)

print(e1.show())
print(e2.show())
print(e3.show())

#display no of employee
print("No of employee: ", Employee.employeeNumber())

Output

Shiv's id is 45
Rajesh's id is 34
JJ Thompson's id is 12
No of employee:  3

Classmethod decorator is used when user want to pass class itself in methods rather than class instance object i.e self. Mostly, the classmethod decorator is used to create additional constructor of class

 

Let's take an example 

class Employee:
	
	def __init__(self, name, id, address):
		self.name = name
		self.id = id
		self.address = address
			
	def display(self):
		print("Name: ", self.name)
		print("Id: ", self.id)
		print("Address: ", self.address)
		
	@classmethod
	def add_new(cls, value):
		name, id, address = value.split(" ")
		return cls(name, id, address)
		
print("First employee: ")
e1 = Employee("Rajesh", 4, "kapilvastu")
e1.display()
print("-----------------------------------\n")

print("Second employee: ")
e2 = Employee.add_new("ram 45 Rautahat")
e2.display()

Output

First employee:
Name:  Rajesh
Id:  4
Address:  kapilvastu
-----------------------------------

Second employee:
Name:  ram
Id:  45
Address:  Rautahat

Here add_new acts as a constructor __init__. This is how classmethod decorator can be used to create multiple constructors.

 

Staticmethod decorator

class Employee:
	
	def __init__(self, name, id, address, age):
		self.name = name
		self.id = id
		self.address = address
		self.age = age
			
	def display(self):
		print("Name: ", self.name)
		print("Id: ", self.id)
		print("Address: ", self.address)
		print("Age: ", self.age)
		
	@staticmethod
	def is_eligible(age):
		if age < 18:
			return "Not eligible to work."
		elif age >= 18 and age < 65:
			return "Eligible to work."
		else:
			return "Not eligible to work."
		
#first employee		
e1 = Employee("Rajesh", 4, "kapilvastu", 23)
e1.display()
print("\n")
print(e1.is_eligible(23))
print("-----------------------------------")

#second employee		
e2 = Employee("Shiv", 41, "Rupandehi", 13)
e2.display()
print("\n")
print(e2.is_eligible(13))
print("-----------------------------------")

#third employee		
e3 = Employee("Thompson", 40, "kathmandu", 90)
e3.display()
print("\n")
print(e3.is_eligible(90))
print("-----------------------------------")

Output

Name:  Rajesh
Id:  4
Address:  kapilvastu
Age:  23


Eligible to work.
-----------------------------------
Name:  Shiv
Id:  41
Address:  Rupandehi
Age:  13


Not eligible to work.
-----------------------------------
Name:  Thompson
Id:  40
Address:  kathmandu
Age:  90


Not eligible to work.
-----------------------------------

Staticmethod is used to define a method static. Static method doesn't take any reference argument whether it is called by instance of class or class itself.  

 

Conclusion

Decorator are used to decorate a existing function that adds functionality to existing function without modifying original function. We can use function as well as class to decorate a function. Some built-in decorators are also available in python such as property decorator, classmethod decorator and staticmethod decorator. Property decorator allows user to access method of class as attribute, classmethod decorator is used for creating more constructor and staticmethod decorator is used to access the function inside class that doesn't belong to class namespace.