Python Decorators – Learn to create and use decorators

We have started with functional programming in Python. Now let’s move on to an advanced concept in functional programming.

In this tutorial, we will learn about decorators in Python and how to create and use Python decorators.

What are Decorators?

Python is an object-oriented language and everything in Python is an object. And therefore, functions are first-class objects.

We can refer to them, pass them to variables and return them from functions. We can pass them as arguments to higher-order functions and define them in functions.

Decorators are like gift wrappers. A decorator is a function that we can use to modify the behavior of a function or a class.

If you want to extend the behavior of a function but don’t want to modify it permanently, you can wrap a decorator on it. This is called metaprogramming, as one part of a program tries to change another at compile time.

Some common decorators in Python are @property, @classmethod and @staticmethod.

How to Create a Decorator in Python?

Let’s say we have nested functions. The outer function takes the function to decorate as an argument and then the inner function calls it.

Code:

>>> def outer(func):
  def inner():
    print("@@@@@@@@@@@@@@@@")
    func()
    print("@@@@@@@@@@@@@@@@")
  return inner

This is the decorator.

Now let’s define the function we want to decorate.

Code:

>>> def sayhi():
  print('Hi')

Code:

>>> sayhi()

Output:

Hi

This function prints Hi.

Now if we call outer with this function sayhi as an argument and then assign this to sayhi, it modifies sayhi.

Code:

>>> sayhi=outer(sayhi)

Now calling it prints two lines of ‘@’ around it.

Code:

>>> sayhi()

Output:

@@@@@@@@@@@@@@@@
Hi
@@@@@@@@@@@@@@@@

You can also give it a new name.

Code:

>>> def outer(func):
  def inner():
    print("@@@@@@@@@@@@@@@@")
    func()
    print("@@@@@@@@@@@@@@@@")
  return inner

Code:

>>> def sayhi():
  print('Hi')

Code:

>>> hello=outer(sayhi)
>>> hello()

Output:

@@@@@@@@@@@@@@@@
Hi
@@@@@@@@@@@@@@@@

Code:

>>> sayhi()

Output:

Hi

Decorators on Functions that Return Something and Take Arguments

The function in the previous example did not return anything. But what if we want to decorate a function that returns a value or takes parameters?

Let’s see.

Code:

>>> def add(a,b):
  return a+b

We have a function add that adds two numbers. This is the decorator:

Code:

>>> def outer(func):
  def inner(a,b):
    print("@@@@@@@@@@@@@@@@")
    print(func(a,b))
    print("@@@@@@@@@@@@@@@@")
  return inner

This decorator’s inner function takes parameters a and b, and then inside, makes a call to func with the arguments a and b. It prints this then.

Code:

>>> add=outer(add)
>>> add(2,3)

Output:

@@@@@@@@@@@@@@@@
5
@@@@@@@@@@@@@@@@

If the inner function calls func(a,b) instead of printing it, it returns None and we only get two lines of ‘@’.

Structure of Python Decorators

Let’s see its syntax again.

We take nested functions for this. The outer function defines the inner function and returns it too, we cannot make a call to it instead of returning it. If we call the inner function instead of returning it:

Code:

>>> def sayhi():
  print('Hi')

Code:

>>> def outer(func):
  def inner():
    print("@@@@@@@@@@@@@@@@")
    func()
    print("@@@@@@@@@@@@@@@@")
  inner()

Code:

>>> sayhi=outer(sayhi)

Output:

@@@@@@@@@@@@@@@@
Hi
@@@@@@@@@@@@@@@@

Code:

>>> sayhi()

Output:

Traceback (most recent call last):
  File “<pyshell#58>”, line 1, in <module>
    sayhi()
TypeError: ‘NoneType’ object is not callable

Applying the decorator calls sayhi and prints the output. And then calling sayhi() raises a TypeError because outer does not return anything.

If we use only one function instead of nested functions:

Code:

>>> def sayhi():
  print('Hi')

Code:

>>> def outer(func):
  print("@@@@@@@@@@@@@@@@")
  func()
  print("@@@@@@@@@@@@@@@@")

Code:

>>> sayhi=outer(sayhi)

Output:

@@@@@@@@@@@@@@@@
Hi
@@@@@@@@@@@@@@@@

Code:

>>> sayhi()

Output:

Traceback (most recent call last):
  File “<pyshell#64>”, line 1, in <module>
    sayhi()
TypeError: ‘NoneType’ object is not callable

And returning func in outer does not help.

Code:

>>> def sayhi():
  print('Hi')

Code:

>>> def outer(func):
  print("@@@@@@@@@@@@@@@@")
  return func
  print("@@@@@@@@@@@@@@@@")

Code:

>>> sayhi=outer(sayhi)

Output:

@@@@@@@@@@@@@@@@

Code:

>>> sayhi()

Output:

Hi

Code:

>>> sayhi()

Output:

Hi

Pie Syntax

If you don’t want to use this line:

sayhi=outer(sayhi), you can use this syntactic sugar to attach a decorator to a function:

Code:

>>> def outer(func):
  def inner():
    print("@@@@@@@@@@@@@@@@")
    func()
    print("@@@@@@@@@@@@@@@@")
  return inner

Code:

>>> @outer
def sayhi():
  print('Hi')

Code:

>>> sayhi()

Output:

@@@@@@@@@@@@@@@@
Hi
@@@@@@@@@@@@@@@@

Chained Decorators in Python

You can apply more than one decorator to a function or class. The order in which you apply them matters.

Code:

>>> def decor1(func):
  def inner():
    print('11111111111111111')
    func()
    print('11111111111111111')
  return inner

Code:

>>> def decor2(func):
  def inner():
    print('22222222222222222')
    func()
    print('22222222222222222')
  return inner

Code:

>>> @decor1
@decor2
def sayhi():
  print('Hi')

Code:

>>> sayhi()

Output:

11111111111111111
22222222222222222
Hi
22222222222222222
11111111111111111

In this example, we have two decorators – decor1 and decor2.

decor1 is applied first and then decor2 is applied.

For this, we just write @decor1 and @decor2 in separate lines.

So in the output, 11111111111111111 is first and then 22222222222222222.

The order in which you apply the decorators matters.

Python Decorators with Arguments

Decorators can take arguments.

Code:

>>> def decor1(func, num):
  def inner():
    print('1'*num)
    func()
    print('1'*num)
  return inner

Code:

>>> def sayhi():
  print('Hi')

Code:

>>> sayhi=decor1(sayhi, num=9)
>>> sayhi()

Output:

111111111
Hi
111111111

Here, we make decor1 print the number ‘1’ 9 times on each side.

Summary

In this TechVidvan’s Python decorators article, we learned about the decorator functions in Python and then we saw how to create, use and chain them.

We learned about the structure, pie syntax, Python decorators with arguments and decorators on functions that return a value or take arguments.