avatarFahadul Shadhin

Summary

The provided content explains the fundamental concepts of Python decorators, illustrating how to create general-purpose decorators using *args and **kwargs to enhance function capabilities without altering their structure.

Abstract

The article delves into the essence of Python decorators, a powerful feature that allows programmers to add functionalities to existing functions or methods without modifying their code. It emphasizes the first-class nature of functions in Python, which enables them to be treated as variables, passed as arguments, and returned from other functions. The author demonstrates the creation of custom decorators, showcasing how they can be applied to functions with and without arguments. The discussion extends to the use of *args and **kwargs to make decorators versatile and capable of handling functions with various numbers and types of arguments, thus creating general-purpose decorators. The article also touches on the distinction between positional and keyword arguments in Python and how these are managed within decorators. Finally, the article provides examples and resources for further learning, advocating for the practical benefits of decorators and their role in writing clean and efficient Python code.

Opinions

  • The author believes that understanding decorators is crucial for Python programmers due to their ability to add functionality to existing code in a clean and maintainable way.
  • Decorators are presented as a metaprogramming tool, suggesting that they are an advanced but essential concept for writing sophisticated Python programs.
  • The article suggests that using *args and **kwargs in decorators is a best practice for creating flexible and reusable decorators that can be applied to a wide range of functions.
  • The author's inclusion of multiple examples and references to further reading indicates a belief in the value of practical, hands-on learning and community resources for mastering Python decorators.
  • By highlighting built-in decorators like @classmethod, @staticmethod, and @property, the author implies that familiarity with both custom and built-in decorators is important for leveraging the full power of Python.
  • The promotion of an AI service at the end of the article suggests the author's endorsement of tools that enhance programming efficiency and offer cost-effective alternatives to more expensive options.

Fundamental of Python Decorators and Importance of *args & **kwargs

Understand the basics of Python decorators and learn how to create general-purpose decorators using *args & **kwargs.

Image by author

Decorators allow us to add more functionalities in a Python function. It is called metaprogramming because one part of the program adds something to another part of the program at compile time.

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.

Datacamp’s definition of a decorator.

Decorators are used on top of a function and start with @ sign. Following is the basic structure to use a decorator:

@decorator_name
def function_name():
    ...

The understanding of decorators heavily relies on some characteristics of functions in python. Functions in Python are first-class citizens. That means…

  • We can pass a function as an argument of another function.
  • Assign a function to a variable.
  • Return a function from another function. In other words, a function is higher-order.

These are the fundamental concepts to understand before diving into decorators. Let's talk about them first.

Function as Argument of Another Function

Let’s create a function and pass it as an argument of another function.

Output:

False

The even_or_odd() function checks if a number is even or odd. We are passing this function as an argument of the function call_function().

Assign a Function to a Variable

Let’s change our code just a little like this…

Output:

False

Here we assigned call_function() to the variable result.

Higher-order Functions

We call a function higher-order when the function can return another function or can be returned from another function. Let’s see an example of that.

This code will print Hello everyone! in the console. Here the function say_hello() is returning the function hello(), and hello() is being returned from say_hello(). So they are both higher-order functions.

Creating a Decorator

Now that we understand all the prerequisites, let’s create our own decorator.

Output:

***************
Hello everyone!
***************

See how the hello_decorator() changes the hello() function. Just putting @hello_decorator before our hello() function allowed us to add extra functionality to the function.

We could also write it like this:

This will also give us the same result. So we can see that decorators are mainly using the characteristics of functions that we discussed before. That functions are first-class citizens and they are higher-order.

Now we have a decorator that we can use in as many functions as we need. Suppose we have another function bye() that we want to decorate like before. We just need to do this:

@hello_decorator
def bye()"
    print("Bye everyone!")

Decorating Functions With Arguments

If we want to add some functionality to a function with arguments, we have to create decorators that can decorate a function with arguments. Suppose we have a function that takes a number as an argument and multiplies it by 2. We want to create a decorator that will tell us if the number was odd or even.

Output:

The number was even
20

In order to decorate multiply_by_two() which receives an argument we had to pass the argument to the wrapper_func() function.

Decorating a Function With Multiple Arguments

Now the question arises if a function accepts more than one argument do we have to pass them to our wrapper function one by one? Or is there a more efficient way? This question will lead us to the concept of general-purpose decorators. We will also understand what *args & *kwargs can do for us.

General Purpose Decorators — Using *args & **kwargs

We will write a function that accepts three numbers as arguments and print their sum value. We will also create a decorator that will show the numbers. We can write the code like this.

Output:

The numbers were 1, 2 and 3
Sum: 6

But there is a better way of doing it. Let’s see…

This will also give us the same result. If we have another function that prints the sum of 5 numbers, the @display_decorator will also work on that too.

Output:

The numbers were (1, 2, 3, 4, 5)
Sum: 15

So, using *args & **kwargs made displey_decorator() a general-purpose decorator.

In Python, we have two types of arguments, positional arguments, and keyword arguments. *args and **kwargs collect all the positional arguments and keyword arguments respectively from our functions and store them in args and kwargs variables. The add_five_numbers() function has five positional arguments. *args stored them in a variable named args. That’s why we were able to print them just by writing print(f’The numbers were {args}’).

Now let’s see an example with keyword arguments.

Output:

Keyword arguments are: {‘n1’: ‘100’, ‘n2’: ‘200’}
This function has two numbers as keyword arguments.

Note that if we passed the keyword arguments when we defined num(), then they would not be stored and we won’t be able to print them.

Summing Up

With the help of *args & **kwargs decorators become general purpose. That way if we need the same kind of decorator in different functions with different arguments, we can write just one decorator. So we can generalize the structure of a decorator like this —

def decorator_name(function):
    def wrapper_function(*args, **kwargs):
        # Put what the decorator will do here.......
        function(*args, **kwargs)
    return wrapper_function
@decorator_name
def function_name(arg1, arg2, arg3……)

Creating our own decorator is a very powerful concept in Python. There are many built-in decorators as well. Like @classmethod, @staticmethod, @property, @login_requiredand many more. These built-in decorators are part of the Python language. So we can use them to add many important features in our code without implementing them from scratch.

Hope this was helpful for you. Thanks for reading.

Some Helpful Resources

If you enjoy reading articles like these, consider becoming a Medium member. This way you get unlimited access to all stories on Medium. If you sign up using my referral link below, I’ll earn a small commission from your $5 monthly fees. This way you can support me as a writer.

Programming
Python
Software Development
Python Programming
Decorators
Recommended from ReadMedium