DEV Community

Cover image for Decorator Functions in Python.
Debojyoti Chatterjee
Debojyoti Chatterjee

Posted on

Decorator Functions in Python.

Alt Text
A decorator function allows you to add or modify an existing function without actually modifying it's structure.

This is a very basic explanation of a decorator function.

But the question here is why should we use a decorator function when we can just change a function as required?

Well, it's not always you have one single function that you might want to modify. Suppose you have a bunch of functions in your project where you want to make a specific change to all the function as a part of your requirement.

Now it would be very tedious to find and modify each and every function and also test each one of them to make sure it does not break your application.

For that you have decorator function using which you can modify it without actually altering any of the code inside the function. Decorator function can have many use cases but is typically used when you want to make minor changes to your existing set of functions.

Let's have a look at the simple example:

def deco_func(val):
    def wrapper():
        print("Trigger")
        print(val)
        print("Kill")
        print("------------------------------")
    return wrapper

holder = deco_func("Hello Python")
print(holder)
holder()
Enter fullscreen mode Exit fullscreen mode

Output:

<function deco_func.<locals>.wrapper at 0x7efcdc4e8550>
Trigger
Hello Python
Kill
------------------------------
Enter fullscreen mode Exit fullscreen mode

The above example is possible in python because the functions here are treated as first class objects, which means that functions can be passed as or used as parameters/arguments.

Here's some quick pointers to keeps in mind:

  • A function is an instance of the Object type.
  • A function can be stored in a variable.
  • A function can be passed as a parameter.
  • A function can return a function.

Now, let's make some minor changes in the above code:

def deco_func(func):
    def wrapper():
        print("Trigger")
        func()
        print("Kill")
        print("------------------------------")
    return wrapper

def func1():
    print("This is Function 1")

def func2():
    print("This is Function 2")

def func3():
    print("This is Function 3")

func1 = deco_func(func1)
func2 = deco_func(func2)
func3 = deco_func(func3)
print(func1)
func1()
func2()
func3()
Enter fullscreen mode Exit fullscreen mode

Output:

<function deco_func.<locals>.wrapper at 0x7f6960526820>
Trigger
This is Function 1
Kill
------------------------------
Trigger
This is Function 2
Kill
-----------------------------------
Trigger
This is Function 3
Kill
-----------------------------------
Enter fullscreen mode Exit fullscreen mode

In the above code we have updated the deco_func and now it accepts a function as an argument.
We have also created three functions that just print a statement.
Now, the line
func1 = deco_func(func1)
allows us to store the deco_func which accepts a parameter func1, all that in a variable func1. Hence, we can now call func1() to get the desired results.
By seeing the above piece of code, you can now figure out a bit, how decorator function works behind the scenes.

So, whenever you are creating a decorator function you have to create this wrapper function/functionality.
The outer function takes the function itself as an argument and the inner wrapper function calls the actual function upon which you are making the modifications.

Below is an example of the above code as a decorator function syntax in Python:

def deco_func(func):
    def wrapper():
        print("Trigger")
        func()
        print("Kill")
        print("------------------------------")
    return wrapper
@deco_func
def func1():
    print("This is Function 1")
@deco_func    
def func2():
    print("This is Function 2")
@deco_func    
def func3():
    print("This is Function 3")

func1()
func2()
func3()
Enter fullscreen mode Exit fullscreen mode

Now, these are sort of dumb functions that just print a statement.
What happens when one of the functions requires one or more parameters to be passed and some doesn't. Or, some functions return a value and some doesn't??

Below is an example when one or more functions requires and arguments/parameters to be passed or one or more functions happen to return some value back when they are called. The whole point of decorator functions is to be able to use in any functions use-case, hence for that we use the unpack or splat operator i.e. * args and ** kwargs:

def deco_func(func):
    def wrapper(*args, **kwargs):
        print("Trigger")
        res = func(*args, **kwargs)
        print("Kill")
        print("------------------------------")
        return res
    return wrapper
@deco_func
def func1(val):
    print("This is Function 1")
    print("Function 1 value: ", val)
@deco_func    
def func2(val1, val2):
    print("This is Function 2")
    return val1 + val2
@deco_func    
def func3():
    print("This is Function 3")

func1(20)
result2 = func2(45, 40)
func3()
print("Function 2 sum value: ", result2)
Enter fullscreen mode Exit fullscreen mode

Output:

Trigger
This is Function 1
Function 1 value:  20
Kill
-----------------------------------
Trigger
This is Function 2
Kill
-----------------------------------
Trigger
This is Function 3
Kill
-----------------------------------
Function 2 sum value:  85
Enter fullscreen mode Exit fullscreen mode

As you can see that the splat operator helps the decorator function to accept none or any number of parameters and the res variable stores the returned value of the decorated function to serve it's purpose.

So, That's all about decorator function. I know it's a bit too much to take at a single reading, but I suggest open your python console or jupyter notebook and follow each line of the code snippets to understand the functionality and it will be a piece of cake for you!

Top comments (0)