Python Decorators 101

Python Decorators 101

This article is part of python decorator series. In this article, we will learn what is decorator and how it works and how to implement a simple decorator function.

Decorator

In Python, decorators are the functions that can change the behavior of other functions(callables) without changing their code. They provide a transparent way of adding functionalities to already defined functions.

Simple decorator function example

def hello_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return wrapper


@hello_decorator
def add(a, b):
    return a + b


if __name__ == '__main__':
    output = add(2, 2)
    print(output)

In this example,

  • the function hello_decorator is added as a decorator for add function.
  • hello_decorator takes a func as an argument, args and kwargs are parameters that are passed to add function.
  • hello_decorator modifies the behavior of add function by returning wrapper(a callable). Since the wrapper is an inner function it has access to func, args, kwargs in its scope. So whenever add function is called. it actually calls the wrapper.
  • Inside the wrapper, we simply execute the function using func(*args, **kwargs) and return the result.

Let's add few print statements in our decorator and see what happens inside,

def hello_decorator(func):
    print(f'Decorator Init :: Begins for {func.__name__}')

    def wrapper(*args, **kwargs):
        print(f'Decorator execution :: Begins for {func.__name__}')
        result = func(*args, **kwargs)
        print(f'Decorator execution :: Ends for {func.__name__}')
        return result

    print(f'Decorator Init :: Ends for {func.__name__}')
    return wrapper


@hello_decorator
def add(a, b):
    return a + b


if __name__ == '__main__':
    output1 = add(2, 2)
    print('Result:: ', output1)

    output2 = add(4, 2)
    print('Result:: ', output2)
Decorator Init :: Begins for add
Decorator Init :: Ends for add
Decorator execution :: Begins for add
Decorator execution :: Ends for add
Result::  4
Decorator execution :: Begins for add
Decorator execution :: Ends for add
Result::  6

How it works

A decorator is a function(callable) that takes another function(callable) as an argument, does some actions, and then returns the argument or callable. Python classes can also be used as decorators.

python-decorator-101.png

What happens under the hood

Lets implement the same decorator but without using the @ syntax.

def hello_decorator(func):
    print(f'Decorator Init :: Begins for {func.__name__}')

    def wrapper(*args, **kwargs):
        print(f'Decorator execution :: Begins for {func.__name__}')
        result = func(*args, **kwargs)
        print(f'Decorator execution :: Ends for {func.__name__}')
        return result

    print(f'Decorator Init :: Ends for {func.__name__}')
    return wrapper



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

# This is equivalent to the @ syntax
add = hello_decorator(add)

if __name__ == '__main__':
    output1 = add(2, 2)
    print('Result:: ', output1)

    output2 = add(4, 2)
    print('Result:: ', output2)

In Python, everything is an object. functions are first-class objects. You can assign them to variables, store them in data structures, pass them as arguments to other functions, and even return them as values from other functions.

In the above example, function add is passed as an argument to hello_decorator and then add function is replaced. Now, calling add means, calling hello_decorator(add). The @ decorator is just an syntactic sugar.

Why decorators?

  • Easy to read - decorators are easy to read and modify without stuffing too much logic in a single function.
  • DRY - Don't Repeat Yourself - decorators makes the code easy to reuse.

There are a lot of builtin decorators available in standard python and many popular tools and frameworks use decorators a lot. Few examples,

  • Flask - @app.route @app.before_request @app.after_request @app.teardown_request
  • Django - @login_required @csrf_exempt @ require_http_methods
  • Python class - @classmethod @staticmethod @property
  • Unittesting mock libraries - @unittest.mock @mock.patch

In the next article, we will improvise this simple decorator function. Stay tuned for upcoming articles. Connect with me on twitter to get my future articles.

Did you find this article valuable?

Support Suresh Kumar by becoming a sponsor. Any amount is appreciated!