Skip to content

Introduction to Python Decorators

Posted on:December 1, 2022

Decorators are a powerful and convenient feature of the Python programming language. They allow you to extend and modify the behavior of existing functions and methods without modifying their code. In this post, we will explore the basics of decorators and demonstrate how to use them in your own Python code.

Table of contents

Open Table of contents

What are Decorators?

Decorators are a way to dynamically alter the behavior of a function or method. They are typically defined as functions that take another function as an argument and return a modified version of that function. This modified version is then called instead of the original function, allowing you to extend or modify the behavior of the original function without modifying its code.

Here is a simple example of a decorator function in Python:

def my_decorator(func):
    def wrapper():
        print('Before calling the function')
        func()
        print('After calling the function')
    return wrapper

This my_decorator function takes a function as an argument and returns a modified version of that function. The modified version, which is defined as the wrapper function, first prints a message, then calls the original function, and then prints another message.

To use this decorator, you would apply it to a function using the @ symbol, like this:

@my_decorator
def my_function():
    print('Inside the function')

This code defines a simple function named my_function that prints a message. The @my_decorator syntax applied to the function indicates that the my_decorator decorator should be applied to the my_function function. This means that whenever the my_function function is called, the my_decorator decorator will be applied to it, and the wrapper function returned by the decorator will be called instead of the original my_function function.

If you call the my_function function, you will see the following output:

Before calling the function
Inside the function
After calling the function

This output shows the messages printed by the wrapper function before and after calling the original my_function function. This demonstrates how decorators can be used to extend and modify the behavior of existing functions without modifying their code.

Decorator Arguments

Decorator functions can also accept arguments. This allows you to customize the behavior of the decorator for each function it is applied to. Here is an example of a decorator function that accepts an argument:

def my_decorator(arg1, arg2):
    def real_decorator(func):
        def wrapper(*args, **kwargs):
            print('Before calling the function')
            print('Decorator arguments:', arg1, arg2)
            func(*args, **kwargs)
            print('After calling the function')
        return wrapper
    return real_decorator

This my_decorator function accepts two arguments, arg1 and arg2, and returns a decorator function. The decorator function accepts a function as an argument and returns a modified version of that function. The modified version, which is defined as the wrapper function, first prints a message, then prints the values of the arg1 and arg2 arguments, then calls the original function, and then prints another message.

To use this decorator, you would apply it to a function using the @ symbol and specify the arguments for the decorator, like this:

@my_decorator('arg1 value', 'arg2 value')
def my_function(arg1, arg2, arg3):
    print('Inside the function')
    print('Function arguments:', arg1, arg2, arg3)

This code defines a function named my_function that accepts three arguments and prints their values. The @my_decorator('arg1 value', 'arg2 value') syntax applied to the function indicates that the my_decorator decorator should be applied to the my_function function, and the specified arguments should be passed to the decorator. This means that whenever the my_function function is called, the my_decorator decorator will be applied to it with the specified arguments, and the wrapper function returned by the decorator will be called instead of the original my_function function.

If you call the my_function function with the arguments 'arg1 value', 'arg2 value', 'arg3 value', you will see the following output:

Before calling the function
Decorator arguments: arg1 value arg2 value
Inside the function
Function arguments: arg1 value arg2 value arg3 value
After calling the function

This output shows the messages printed by the wrapper function before and after calling the original my_function function, as well as the values of the arg1 and arg2 arguments passed to the decorator and the values of the arg1, arg2, and arg3 arguments passed to the my_function function. This demonstrates how decorator arguments can be used to customize the behavior of a decorator for each function it is applied to.

Decorator Classes

In addition to decorator functions, Python also supports decorator classes. Decorator classes are classes that implement the __call__ method, which allows them to be called like a function. This makes it possible to define a decorator as a class, rather than a function.

Here is an example of a decorator class in Python:

class MyDecorator:
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('Before calling the function')
            print('Decorator arguments:', self.arg1, self.arg2)
            func(*args, **kwargs)
            print('After calling the function')
        return wrapper

This MyDecorator class defines a constructor that accepts two arguments, arg1 and arg2, and stores them as instance variables. The class also defines the __call__ method, which is called when the decorator is applied to a function. This method accepts a function as an argument and returns a modified version of that function. The modified version, which is defined as the wrapper function, first prints a message, then prints the values of the arg1 and arg2 instance variables, then calls the original function, and then prints another message.

To use this decorator class, you would apply it to a function using the @ symbol and specify the arguments for the decorator, like this:

@MyDecorator('arg1 value', 'arg2 value')
def my_function(arg1, arg2, arg3):
    print('Inside the function')
    print('Function arguments:', arg1, arg2, arg3)

This code defines a function named my_function that accepts three arguments and prints their values. The @MyDecorator('arg1 value', 'arg2 value') syntax applied to the function indicates that the MyDecorator decorator class should be applied to the my_function function, and the specified arguments should be passed to the constructor. This means that whenever the my_function function is called, an instance of the MyDecorator class will be created with the specified arguments, and the __call__ method of the instance will be called, which will return the wrapper function and replace the original my_function function.

If you call the my_function function with the arguments 'arg1 value', 'arg2 value', 'arg3 value', you will see the same output as in the previous example, because the behavior of the decorator is the same in both cases.

Conclusion

Decorators are a useful and powerful feature of the Python programming language. They allow you to extend and modify the behavior of existing functions and methods without modifying their code. Decorators are typically defined as functions or classes that accept a function as an argument and return a modified version of that function. Decorators can also accept arguments, which allows you to customize the behavior of the decorator for each function it is applied to.

In this post, we have explored the basics of decorators and demonstrated how to use them in your own Python code. In addition to the examples provided in this post, there are many other ways in which you can use decorators to simplify and improve your code. For example, you can use decorators to:

Further Reading

If you want to learn more about decorators in Python, here are some resources that you may find useful: