Python Decorators


In this article, you will learn about Python decorators, the underlying mechanism behind decorators and how are decorators created in Python.

Decorators in Python: Introduction
Underlying mechanism of functions
How to create Python decorator?
Python decorator with parameters
Python decorator classes
 python decorators

Python Decorators: Introduction


Python decorators are one of the powerful features of Python which allows you to dynamically change the behavior or extend the functionality of a function.

Decorators are perfect for the situation where we need to extend the functionality of a function without actually modifying it.

Decorators in their simplest form look somewhat like this.

@example_decorator
def example_func(foo):
  #function body
  return something

Where, example_decorator is a decorator and example_func is to be decorated.

Python decorators are hard to get along with. Once understood we can do so many powerful things but initially, programmers find it difficult to understand.

So before jumping into details of Python decorators, you need to learn some of the underlying mechanism of functions in Python that must be understood first.

Functions are the first class objects

Everything in Python is an object and functions(even classes as well) are no exceptions to it. Functions are also objects with attributes.

A function can be assigned to any variable as a normal object. Functions can also be passed to functions as arguments and yes, they can also be returned from a function as values.

Apart from this, a function can also be defined inside another function called nested functions. Let’s take an example of each to demonstrate these above-mentioned features.

A function can be assigned to a variable as normal object

def func():
   print ('Hello')
obj = func()
obj
#ouput: Hello

A function can be passed as argument to another function

def func1(name):
   print ('Hello', name)

def func2(func)
   name = 'John'
   return func(name)

Let’s pass function func1 as the argument in func2 in the interpreter.

>>> func2(func1)
Hello John

A function can be defined under another function and function can also return another function: Nested function

def func1():
 def func2():  #nested function
   print ('Hello')
 return func2() #returning function

Let’s try this on the interpreter.

>>> func1()
Hello

Learn about Python closures to know in detail about nested function and enclosing scope.

How to create Python decorators?


So, now finally let’s dig into how to actually create Python decorators.

A decorator is just a callable that takes a function as an argument and returns a replacement function.  Anything that implements __call__() function behind the scene is called callable and decorator is one of them.

Previously we mentioned that with decorators we can extend the functionality of a function without actually modifying it. Let’s see and implement this in a program.

def display():
  print ('Harry')

#defining decorator function
def decorator_func(func):
   def inner_func():
      print ('I am decorated Harry')
   return inner_func()

#Original function
display()

#After decorating with decorator_func
decorator_func(display)

Output

Harry
I am decorarted Harry


Explanation

As you can see in above example, we have created a decorator where we have passed the function display as an argument to the decorator function. The decorator function adds an extra functionality to that function and returns the result.

Notice that the original function is not changed.

Hence, this feature of adding extra functionality to a function without actually modifying it is called decorator in Python.

But this is not the how the decorators are syntactically implemented in Python. This was just to make you understand the working mechanism of a decorator.

Here is the proper implementation of decorator in a Python program.

Python Decorator’s Syntax

The decoration occurs in the line just before the function. The symbol ‘@’ is followed by the decorator functions name.

Here is the implementation of decorator in the example we example above.

#defining decorator function
def decorator_func(func):
   def inner_func():
      print ('I am decorated Harry')
   return inner_func()

@decorator_func
def display():
   print ('Harry')

Output

I am decorarted Harry

Now, that’s the proper syntactical implementation of decorator in Python.

In above code

@decorator_func
def display():
   print ('Harry')

is equivalent to

def display():
   print ('Harry')
display = decorator_func(display)

Python decorators with parameters

Decorators can be both parameterized or non-parameterized. We have already discussed decorators without parameters. Now well will learn about decorators with parameters.

Let’s create a simple decorator to find the square of a number. Here both the function and the decorator will have parameters.

def perfect_square(str_param):
  def middle_decorator(func):
    def inner(x):
      print (str_param,' of ',x)
      print ('The sqaure of', x ,' is ')
      return func(x)
    return inner
  return middle_decorator

@perfect_square('Find the square')
def display_square(a):
  return a*a

Let’s see what happens when we try the function.

>>> display_square(2)
Find the square of 2
The square of 2 is 
4

So far we have used functions as decorators. But in Python, we can use classes as decorators as well. Let’s learn how we can use Python classes as decorators.

Python Decorator Classes


We already know that decorator simply is a callable object which takes functions as arguments. In Python, we can also define other objects as a callable using __call__() method.

Here we will learn how to make a class callable using __call__() function and use that class as a decorator.

Let’s make a decorator class used to decorate a function for finding the square of a number.

class decorator_class:
   def __init__(self, x):
        self.x = x
        
   def __call__(self, a):
        print("The square of", a, 'is')
        print (self.x(a))

@decorator_class
def display_square(a):
  return a*a

Let’s see what happens when we try this on interpreter.

>>> display_square(2)
The square of 2 is 
4

That’s pretty much what we expected. What we did was defined a decorator class and used it to decorate the function display_square.