Understanding Python Decorators, with Examples

This article will help you understand the concept of decorators in Python programming and make the best use of them. We’ll cover what Python decorators are, what their syntax looks like, how to recognize them in a script or framework, and how to implement them yourself.

a function decorator Python is just a function that takes another function as an argument, extending the functionality of a decorated function without changing its structure. A decorator wraps another function, extends its behavior, and returns it.

The concept of decorators in Python helps in maintaining your code dry, A function decorator avoids unnecessary duplication in your codebase, as some repeated bits of code can be pulled together to become a function decorator. As you move into using Python for development, decorators can help with analytics and logging. They are also important for setting up validation and runtime checks.

As we move forward, I will assume that you have a basic understanding of Python’s functions and programming and that you have at least Python 3.8 installed on your device.

Things to understand before getting into Python decorators

In Python, functions are first-class objects, which means they can receive arguments or be passed as arguments. To fully understand the concept of decorators, there are a few things you need to understand.

A function is an object, which means it can be assigned to another variable.

def greet():
    print("Hello John")
    
greet_john = greet
greet_john()
>>>
Hello John

Always remember that everything in Python is an object. Just as you assign a value to a variable, a function can also be assigned to a variable where necessary. This is important as you learn about decorators.

A function can be returned from another function

def greet():
    def greeting_at_dawn():
        print("Good morning")
        
    return greeting_at_dawn

salute = greet()
salute()
>>>
Good morning

In Python an inner function can be returned from an outer function. This is part of the functional programming concepts you will see.

A function can be passed as an argument to another function

def greet_some(func):
    print("Good morning", end=' ')
    func()

def say_name():
    print("John")

greet(say_name)
>>>
Good morning John

A function that receives a function argument is called a . is referred to as higher order function,

The points listed above are important to keep in mind when learning to implement decorators and use them effectively in Python programs.

How Python Decorators Work

A simple decorator function starts with a function definition, a decorator function, and then a nested function within the outer wrapper function.

Always keep these two main things in mind when defining a decorator:

  1. To implement decorators, define an external function that takes a function argument.
  2. Nesting a wrapper function within the outer decorator function, which also wraps the decorated function.

This is what the most basic decorator function looks like in the code snippet below:

def increase_number(func):
    def increase_by_one():
        print("incrementing number by 1 ...")
        number_plus_one = func()  + 1
        return number_plus_one
    return increase_by_one 
    
def get_number():
    return 5
    
get_new_number = increase_number(get_number)
print(get_new_number())
>>>
incrementing number by 1 ...
6

Looking at the above code, the outer function increase_number – also known as decorator – receives a function argument func, increase_by_one wrapper function is where decorated get_number function is found. The decorator is assigned to another variable. This is what a decorator syntax looks like when using Python decorators. However, there is a much simpler way to represent decorators.

A simple decorator function is easily recognized when it starts @ prefix, together with the function decorated below. The previous example can be rephrased to look like this:

def increase_number(func):
    def increase_by_one():
        print("incrementing number by 1 ...")
        number_plus_one = func()  + 1
        return number_plus_one
    return increase_by_one 
    
@increase_number    
def get_number():
    return 5

print(get_number())
>>>
incrementing number by 1 ...
6

The examples show that a decorator extends the functionality of its function argument.

Decorator function with parameters

There are cases where you may need to pass parameters to the decorator. The way around this is to pass a parameter to the wrapper function, which is then passed to the decorated function. See the following examples:

 def multiply_numbers(func):
    def multiply_two_numbers(num1, num2):
        print("we're multiplying two number  and ".format(num1, num2))
        return func(num1, num2)
        
    return multiply_two_numbers

@multiply_numbers
def multiply_two_given_numbers(num1, num2):
    return f'num1 * num2 = num1 * num2'
    
print(multiply_two_given_numbers(3, 4))
 >>>
we're multiplying two number 3 and 4
3 * 4 = 12

Passing parameters to the inner function or nested function makes it even more powerful and robust, as it gives more flexibility to manipulate the decorated function. any number of arguments (*args) or keyword arguments (**kwargs) can be passed to the decorated function. *args allows the collection of all positional arguments, whereas **kwargs is for all keyword arguments required during a function call. Let’s look at another simple example:

def decorator_func(decorated_func):
    def wrapper_func(*args, **kwargs):
        print(f'there are len(args) positional arguments and len(kwargs) keyword arguments')
        return decorated_func(*args, **kwargs)
        
    return wrapper_func

@decorator_func
def names_and_age(age1, age2, name1='Ben', name2='Harry'):
    return f'name1 is age1 yrs old and name2 is age2 yrs old'
    
print(names_and_age(12, 15, name1="Lily", name2="Ola"))
>>>
There are 2 positional arguments and 2 keyword arguments
Lily is 12 yrs old and Ola is 15 yrs old

From the above example, *args Creates an iterator of positional arguments as a tuple, whereas **kwargs Creates a dictionary of keyword arguments.

Multiple decorators or decorator chaining in Python

There are several options to explore when using function decorators in your Python project. Another use case would be to chain (two or more) decorators in a function. A function can be decorated with more than one decorator, and this is achieved by stacking one decorator on top of another in no particular order. You will have the same output regardless of the order in which multiple decorators are placed on top of each other, as seen in the following example:

def increase_decorator(func):
    def increase_by_two():
        print('Increasing number by 2')
        new_number = func()
        return new_number + 2
        
    return increase_by_two
    

def decrease_decorator(func):
    def decrease_by_one():
        print('Decreasing number by 1')
        new_number = func()
        return new_number - 1
        
    return decrease_by_one
    
@increase_decorator
@decrease_decorator
def get_number():
    return 5
    
print(get_number())
>>> 
Increasing number by 2
Decreasing number by 1
6

Real life use cases of Python decorators

A very popular way to use decorators in Python is the time logger. It helps a programmer to know how long a function takes to execute as a way of measuring efficiency.

memoir Another cool way is to use decorators in Python. The results of function calls that are repeated without any change can be easily remembered when performing calculations later. You can easily remember a function with decorators.

Built-in Python decorators like @classmethod (class method), @staticmethod (static method), and @property In Python’s OOP decorator pattern is very popular.

conclusion

Python decorators apply the DRY principle of software engineering because they serve as reusable code. Think of several Python functions that you can refactor for decorators. In this article, we have explored the different forms of decorators. There are also class decorators, although we haven’t looked at them here.

Decorators make it easy to add additional functionality to a simple function, method, or class without changing its source code, while keeping your code DRY. Try decorating functions on your own to better understand the decorator pattern.

Leave a Reply