Introduction
The 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 an object including function, method, and class. Names that we define are just identifiers 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 the same output. Both fun and f2 refer to the same function object.
In python, a 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 the first function is passed to the dec function. Inside the dec function, first, we print a message that says it is from the dec function and we called a function that is passed to it which is actually the first function.
In python, the 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 the Outsider function, another function inner is defined. The Outsider function returns the whole inner function. And whenever the Outsider function is called the inner function is returned.
Getting started with a 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, the div function is decorated by div_decorator which adds the exception-handling part. Inside the decorator function, we define a new function named inner which does the exception-handling part and returns called function. The above format is not a standard way to create a 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 is the 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 changes the string into the 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 lengths of the argument. We use *args so that decorator can handle any length of input argument.
Two functions having a number of argument 2 and 3 is decorated. The inner function of the decorator takes the argument and checks whether the elements are zero or not rather than the first element. If the condition matches it returns the message else it returns the passed function itself.
Multiple decorators to a 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 converts a string to a title case and the latter one splits by space. At first, decorator1 decorates a 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
The class can also be used to decorate a function. For this __call__ method must be used because when the user tries to create an object that acts like a function, the class decorator must return an object that acts like a function.
Let’s take another example.
Suppose there is a function that returns the cube of a number. If the user wants the function to return the cube of as many numbers passed to it without affecting the original function, can be done using the 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 a cube of a single number. Then it is decorated by using a class decorator to return cubes of multiple numbers.
Till now we’re dealing with user-defined decorators 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 the method of class as an attribute. If the user wants to access the method as an attribute property decorates come into the 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 the 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 the user wants to pass the class itself in methods rather than class instance object i.e self. Mostly, the classmethod decorator is used to create an additional constructor of a 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 a 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. The static method doesn’t take any reference argument whether it is called by an instance of class or class itself.
Conclusion
Decorators are used to decorate an existing function that adds functionality to an existing function without modifying the original function. We can use function as well as class to decorate a function.
Some built-in decorators are also available such as property decorator, classmethod decorator, and staticmethod decorator.
Property decorator allows the user to access the method of class as an attribute, classmethod decorator is used for creating more constructors, and staticmethod decorator is used to access the function inside the class that doesn’t belong to the class namespace.
Happy Learning 🙂