Guide to using decorators in Python

This post is lesson 19 of 54 in the subject Python Programming Language

1. Characteristics of functions in Python

Before learning about decorators in Python, it is important to know the characteristics of functions in Python. Functions in Python have the following characteristics:

  • A function is an object of the function class.
  • A variable can reference a function.
  • The argument of a function can be another function.
  • The return value of a function can be another function.
  • Data structures like hash tables, lists, etc. can be used to store functions.

It is important to remember that a function in Python is also an object. Let’s look at the following examples to understand more about functions in Python.

Variables reference a function

def myFunction(msg):
    print(msg)

# Call myFunction normally
myFunction("Welcome to Gochocit.com!")

# Val variable reference to myFunction function
val = myFunction
val("Hello all, welcome to Gochocit.com!")

Result

Welcome to Gochocit.com!
Hello all, welcome to Gochocit.com!

The argument of a function can be another function

def increase(x):
    """Increase x"""
    return x + 1

def decrease(x):
    """Decrease x"""
    return x - 1

def operate(func, x):
    """Execute an operator"""
    result = func(x)
    return result

a = operate(increase, 2)
print(a)

b = operate(decrease, 1)
print(b)

Result

3
0

The return value of a function can be another function

def create_adder(x):
    # Inner function
    def adder(y):
        return x+y
    return adder

# Call create_adder function
# Add_15 refer to adder function
add_15 = create_adder(15)

# Call adder function
print(add_15(10))

Result

25

In the above example, the function adder() is inside the function creater_adder(). The return value of the function creater_adder() is the function adder(). The statement add_15 = create_adder(15) means calling the function create_adder(15) with the argument 15. This function returns the function adder(), so add_15 refers to the function adder(). Then, the function adder() is called by the variable add_15 with the statement add_15(10) with the argument 10. Finally, the result of the statement return x+y is printed.

2. What is Decorator in Python?

After knowing the characteristics of functions in Python, what is the decorator? A decorator is a feature in Python that is essentially a function that can modify another function. Decorators are very convenient when we want to extend the functionality of a function without changing it.

When using a decorator, we can call it “metaprogramming” because part of the program tries to modify another part of the program at compile time. Let’s see the example below:

def make_hello(func):
    def inner():
        print("Hello all,", sep='')
        func()
    return inner

def welcome():
    print("Welcome to Gochocit.com!")

# Call welcome function normally
print("#Result1")
welcome()

# Pass welcome function as an argument of make_hello function
# Hello variable refer to inner function (returned result of make_hello function)
print("#Result2")
hello = make_hello(welcome)
hello()

Result

#Result1
Welcome to Gochocit.com!
#Result2
Hello all,
Welcome to Gochocit.com!

In the above example, the statement hello = make_hello(welcome) means calling the function make_hello() with the argument welcome(). The function make_hello() returns the function inner(). Therefore, the variable hello refers to the function inner() in the function make_hello().

Then, the statement hello() essentially calls the function inner(). The function inner() executes and returns the result as above.

If we use welcome instead of hello to refer to the return value of the make_hello() function, what will happen? Let’s see the program below:

def make_hello(func):
    def inner():
        print("Hello all,", sep='')
        func()
    return inner

def welcome():
    print("Welcome to Gochocit.com!")

# Call welcome function normally
print("#Result1")
welcome()

# Pass welcome function as an argument of make_hello function
# Welcome identifier refer to inner function (returned result of make_hello function)
# Welcome identifier is same name with welcome function)
print("#Result3")
welcome = make_hello(welcome)

# Call inner function (not call welcome function)
welcome()

Result

#Result1
Welcome to Gochocit.com!
#Result3
Hello all,
Welcome to Gochocit.com!

You will see that the program result is the same. But there is a big difference between the statements welcome() in #Result1 and #Result3. They are quite different. The statement welcome = make_hello(welcome) has changed the execution result of welcome().

In this case, the function make_hello() is called a decorator. This function modifies and extends the functionality of the function welcome(). In Python, instead of writing the statement welcome = make_hello(welcome), Python supports another way to write it using the @ character.

@make_hello
def welcome():
    print("Welcome to Gochocit.com!")

is equivalent to:

def welcome():
    print("Welcome to Gochocit.com!")
welcome = make_hello(welcome)

Example:

def make_hello(func):
    def inner():
        print("Hello all,", sep='')
        func()
    return inner

# Make_hello decorated for welcome
@make_hello
def welcome():
    print("Welcome to Gochocit.com!")

welcome()

Result

Hello all,
Welcome to Gochocit.com!

3. Decorator with parameters in Python

In the above example, the decorated function is the function welcome with no parameters. But there are also cases where decorated functions have parameters. For example, we have the function divide() below:

def divide(a, b):
    return a/b

# OK
print("1/2 = ", divide(1, 2))

# Error
print("1/0 = ", divide(1, 0))

Result

1/2 =  0.5
Traceback (most recent call last):
  File "c:\\\\\\\\python-examples\\\\\\\\sumPython.py", line 7, in <module>
    print("1/0 = ", divide(1, 0))
  File "c:\\\\\\\\python-examples\\\\\\\\sumPython.py", line 2, in divide
    return a/b
ZeroDivisionError: division by zero

The error here is “cannot divide by 0“. We can use a decorator to check for errors for the divide() function as follows:

def smart_divide(func):
    def inner(a, b):
        print("I am going to divide", a, "and", b)
        if b == 0:
            print("Whoops! cannot divide")
            return

        return func(a, b)
    return inner

@smart_divide
def divide(a, b):
    print(a/b)

divide(1, 2)
divide(1, 0)

Result

I am going to divide 1 and 2
0.5
I am going to divide 1 and 0
Whoops! cannot divide

4. Chaining decorators in Python

A function can have multiple decorators. For example:

def function1(func):
    def inner():
        x = func()
        return x * x
    return inner

def function(func):
    def inner():
        x = func()
        return 2 * x
    return inner

# Function1(function(num))
@function1
@function
def num():
    return 10

print(num())

Result

400

In the above example:

@function1
@function
def num():
    return 10

is equivalent to:

def num():
    return 10
num = function1(function(num))

You can change the order of the decorators of a function to get different results.

5/5 - (1 vote)
Previous and next lesson in subject<< What is a lambda function in Python?Using module and package in Python >>

Leave a Reply

Your email address will not be published. Required fields are marked *