Python Decorators 101
Tags: python, programming, beginners
Published: 2022-04-18 06:25:58

> 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
```python
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, 

```python
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrdzl6l60gqe94lrxeh5.png)

## What happens under the hood
Lets implement the same decorator but without using the @ syntax. 
```python
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.

## Most popular Examples
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](https://twitter.com/sureshdsk) to get my future articles.