---
# System prepended metadata

title: Python Decorators

---

# Python Decorators

![Decorators in Python](https://miro.medium.com/max/1400/1*nphtlrDbU-l1Tlfsp_nlvg.jpeg)

## Introduction
Decorators in Python provide us with a way to **expand the functionality of any existing function**. A decorator is written before the functions whose functionality is to be expanded. The decorator is just another function that takes in as an argument another function **adds some logic before and after the function** and returns the newly enhanced function.

## Prerequisites for learning decorators
To fully understand decorators in python we must know a few things.

### 1. Functions as First-Class Citizens
In python **all the functions are objects**, and thus they are as flexible as any other variable in the code. Thus, a function in python can be **passed as an argument** to another function, **returned as a result** of another function, **assigned to a variable**, and its **properties can be modified** using the dot(.) operator.

### 2. Closures in Python
A closure in python is an **inner function returned from inside of another function** that has the **context (local variables) of the outer function** from which it has been returned. Following function ```closure_fun()``` is an example of a closure function.
```python=
def outer_function(message):
    """This is the outer function"""
    o_string = "Hello" # local variable of outer function
    def inner_function():
        """This is the nested inner function"""
        in_string = "See Ya"
        print(o_string, message, in_string) # outer functions variables
    return inner_function  # returns the nested function

# Now let's try calling this function.
closure_fun = outer_function("People")
closure_fun()
```
###### Output: ```Hello People See Ya```

_It is because of this great property of Closures to retain the context of its outer function even after its execution is completed, that the Closures are used in Decorators._

## Functions in Python
Since functions are First-Class Citizens in python they are treated like any other object.
* In the code below **```variable_one``` is given the reference of a ```function_one```**, so the variable also behaves like a function and can be called by placing parentheses as a suffix to the variable name

```python=
def function_one(message):
  print(message)

variable_one = function_one # referencing function to variable

function_one("hello") # direct function call
variable_one("there") # function called through variable 
```
###### Output: ```hello there```

* A function can also be **passed as an argument** to another function, the example below shows how ```function_one``` was passed as an argument to another ```function_two``` and then called from within.

```python=
def function_one(message):
    print(message)
    
def function_two(function):
    function("executing passed function")
    
function_two(function_one) # passing function as argument
```
###### Output: ```executing passed function```

* A **function can also be returned from another function**, this allows us to create dynamic subroutine calls based on run-time context. Here the function ```print_odd``` or ```print_even``` is returned based on the user input and then executed. Notice that **for returning a function we _do not use parenthesis_**

```python=
def print_even():
    print("The number is even")
def print_odd():
    print("The number is odd")

def choose_function(number):
    if number % 2 == 0:
        return print_even # returning function print_even
    else:
        return print_odd # returning function print_odd

number = int(input("Enter a number: "))
function_to_call = choose_function(number)
function_to_call() # calling the returned function
```
###### Output:
```
Enter a number: 5
The number is odd

Enter a number: 110
The number is even
```

* Another example demonstrates the declaration of a **function inside another function**, this is called an **inner function** in python. This inner function **has access to local variables of the enclosing function**.

```python=
def outer_function():
    var_one = "Hey"

    def inner_function():
        print(var_one, "there!")

    inner_function()

outer_function()
```
###### Output: ```Hey there!```

## Decorating functions with parameters
A function with parameters can be decorated using the concepts of functions as first-class citizens. Here, by decorating a function we practically replace the function with an improved version of itself.

Here is how a function can be decorated

```python=
def subtract(a, b):
    """Basic Subtraction Function"""
    return a - b

def positive_subtract(function):
    """
    This function converts basic 
    Subtraction function to return only positive answer
    """
    def inner_function(a, b):
        if a < b:
            a, b = b, a
        return function(a, b)
    return inner_function # improved function returned

subtract = positive_subtract(subtract)
answer = subtract(12, 34)
print(answer)
```
###### Output: ```22```

## Syntactic Decorator
In the previous example we have decorated ```subtract()``` function by using an assignment based syntax, which can be written as:

```python=
subtract = positive_subtract(subtract)
```

Python comes with a better way of writing the above code, **called pie syntax, we use ```@decorator_name``` notation to decorate a function**. The above code can be rewritten using pie syntax as:

```python=
def positive_subtract(function):
    """
    This function converts basic 
    Subtraction function to return only positive answer
    """
    def inner_function(a, b):
        if a < b:
            a, b = b, a
        return function(a, b)
    return inner_function # improved function returned

@positive_subtract
def subtract(a, b):
    """Basic Subtraction Function"""
    return a - b

answer = subtract(12, 34)
print(answer)
```
###### Output: ```22```


## Reusing Decorator
Decorators in Python are like any other function in python which means that we can save logically related decorators in one ```.py``` file and _import  that file into another python script_. This file can be considered as a module and allows **decorators to be reused like any other module** in python.

```python=
# File : logger_module.py
def logger(function):
    def inner():
        print("Before executing {} function".format(function.__name__))
        function()
        print("After executing {} function".format(function.__name__))
    return inner
```
```python=
from logger_module import logger

@logger
def say_hello():
    print("Hello there")

say_hello()
```

###### Output:
```
Before executing say_hello function
Hello there
After executing say_hello function
```

## Decorator with arguments
In the above example, the decorated ```say_hello()``` function does not take any argument. If we try to decorate a function that takes arguments with the ```@logger``` decorator then, we will encounter an error.

```python=
from logger_module import logger

@logger
def say_hello(name):
    print("Hello there", name)

say_hello("Programmer")
```
###### Output:
```TypeError: logger.<locals>.inner() takes 0 positional arguments but 1 was given```

To allow a decorator to work with a function that takes arguments, we need to use **```*args``` and ```**kwargs``` variables** inside the decorator. They **allow the decorator to work with functions with any number of arguments**.

```python=
# File : logger_module.py
def logger(function):
    def inner(*args, **kwargs):
        print("Before executing {} function".format(function.__name__))
        function(*args, **kwargs)
        print("After executing {} function".format(function.__name__))
    return inner
```
```python=
from logger_module import logger

@logger
def say_hello(name):
    print("Hello there", name)
    
@logger
def say_bye(name, surname):
    print("Bye", name, surname)

say_hello("Programmer")
say_bye("John", "Doe")
```
###### Output:
```
Before executing say_hello function
Hello there Programmer
After executing say_hello function
Before executing say_bye function
Bye John Doe
After executing say_bye function
```

## Returning Values from Decorated Functions
A decorated function can not only add extra functionality to the inner logic of a function, but it can also augment the return value of that function.

```python=
def decorate(function):
    def inner(*args, **kwargs):
        return "Adding functionality to {}\n{}".format(function.__name__, function(*args, **kwargs))
    return inner

@decorate
def greetings(name):
    return "Howdy {}".format(name)

print(greetings("Programmer"))
```
###### Output:
```
Adding functionality to greetings
Howdy Programmer
```

## Fancy Decorators
Expanding on the idea of Decorators in python, there are various scenarios where the use of decorators is appreciated.

### 1. Class Decorators
Python provides us with three class decorators ```@calssmethod```, ```@staticmethod```, and ```@property```. The decorators```@calssmethod``` and ```@staticmethod``` are used to create a **class-level function** inside a class. The **setters** and **getters** for class attributes are defined by the ```@property``` decorator.


```python=
class Employee:
    def __init__(self, name):
        self.name = name

    @staticmethod  
    def hello():  
        print("Hello Employees")

    @property  
    def display(self):  
        return "Employee {}".format(self.name)
  
employee = Employee("John Doe")  
print("Name:", employee.name)
print(employee.display)
employee.hello()  
Employee.hello()  
```
###### Output:
```
Name: John Doe
Employee John Doe
Hello Employees
Hello Employees
```


### 2. Stateful Decorator
It is usually required that a decorator remembers some information between various decorator calls. Decorator allows us to add some attributes so as to keep track of information between various executions.

```python=
# File : counter_module.py
def counter(function):
    def inner(*args, **kwargs):
        inner.times += 1
        print("{} executed {} times.".format(function.__name__, inner.times))
        return function(*args, **kwargs)
    inner.times = 0
    return inner
```
```python=
from counter_module import counter
@counter
def hello():
    print("hello there")

hello()
hello()
hello()
```
###### Output:
```
hello executed 1 times.
hello there
hello executed 2 times.
hello there
hello executed 3 times.
hello there
```

## Classes as Decorators
An improvement on the stateful decorators is Classes as Decorators. A **class is by far the best way to maintain states** for most use case scenarios. To use a class as a decorator we **must make the class callable** by implementing the ```__call__()``` function inside the class, also the constructor ```__init__()``` of the class should **take a function as an argument**.

```python=
class Counter:
    def __init__(self, function):
        self.function = function
        self.times = 0

    def __call__(self, *args, **kwargs):
        self.times += 1
        print("{} executed {} times.".format(self.function.__name__, self.times))
        return self.function(*args, **kwargs)

@Counter
def hello():
    print("hello there")

hello()
hello()
hello()
```
###### Output:
```
hello executed 1 times.
hello there
hello executed 2 times.
hello there
hello executed 3 times.
hello there
```

## Chaining Decorators
We can add multiple decorators to a single function by stacking them on top of one another. This is also called **Nesting Decorators**. Note that the **decorators are executed in the order they are written**.

```python=
from logger_module import logger
from counter_module import counter

@logger
@counter
def hello(name):
    print("hello", name)

@counter
@logger
def bye():
    print("See ya")

hello("Decorator")
bye()
bye()
```
###### Output:
```
Before executing inner function
hello executed 1 times.
hello Decorator
After executing inner function
inner executed 1 times.
Before executing bye function
See ya
After executing bye function
inner executed 2 times.
Before executing bye function
See ya
After executing bye function
```
As we can see in the output the logic inside decorators was executed in the same order in which they are written before the function name.

## Key Notes
* The decorator is a function that adds some logic to an existing function to enhance its functionality.
* In python, functions are objects which can be passed as an argument, returned as a result, or assigned to a variable.
* A closure is an inner function that retains the context of its outer function.
* An easier way is to use pie syntax ```@decorator_name``` to decorate a function.
* Decorators like any other functions in python can be put in a module and reused.
* We use ```*args``` and ```**kwargs``` variables to allow the decorator to work with functions with any number of arguments.
* Decorators can be augmented to retain information between multiple calls making them stateful decorators.
* More than one decorator can be used with the same function by nesting the decorators