Introduction To Decorators In Python
In this article, we will discuss in detail about what are python decorator, why we need them and finally how to create python decorators.
Table of Content
Best-suited Python for data science courses for you
Learn Python for data science with these high-rated online courses
What is Python Decorator?
Python decorator is a function that takes a function as an input and returns a new function as an output.
In simple terms, decorators take in a function, add some functionality to the existing function and return it.
It is used to modify the function’s behavior without permanently changing it.
Mainly, decorators are used for logging, timing, and verifying permission. It can also be used to remove duplication in the program.
Usually, decorators are called before the function you want to decorate.
Must Check: What is Python?
Prerequisites for Decorators
For a clear understanding of decorators in python, we must have to know some concepts that will be used while creating decorators:
- How to create a function in python?
- Concept of passing a function as an argument to another function
- Concept of inner function – Function defined inside another function
Must Check: Functions in Python
Must Check: Python Online Course & Certification
Why do we need Python Decorators?
If there is any common behavior between more than one function or we want to change the parameter (or argument) of any pre-defined functions, we can use the decorator.
Let’s understand the need for the python decorator with a simple example:
Example: Find the factorial.
def fact(n): if n == 0: return 1 else: return (n*fact(n-1)) #this is a recursive formula
In the above program, we find the factorial for any given value of n.
The above program will be good for every non-negative integer.
But what if we take the value of n is negative, it will return the error, or it will not compute.
Now, What??
We will use a decorator to overcome this issue.
So, let’s check how to use a decorator in python to fix this issue.
How to create Python Decorators?
- Define an outer function that takes a function(here fact) as an argument
- Create an inner function with the required new features
- Use @ symbol along with the name of the decorator function
Now, let’s create a decorator for our above example.
def factorial_decorator_function(fun): def inner (n): print ("factorial of", n, "is") if n < 0: print ("Enter positive values only") return return fun(n) return inner @factorial_decorator_functiondef fact(n): if n == 0: return 1 else: return (n*fact(n-1)) #this is a recursive formula
In the above example, factorial_decorator_function is a decorator.
Now we can see that the decorator function added new functionality (i.e. If we input the negative value to find the factorial, it will always return the message “Enter the Positive Values Only”) to the original function.
If we look closely, the parameter of the inner function (inside the decorator) is precisely the same as the parameter of the function.
Working with Python Decorators
Example:
def decorator(func):
#The wrapper function inside #which the argument is called def wrapper(): print("This statement is printed before the function is called") func() print("This statement is printed after the function is called") return wrapper def hello(): print("Hello, the function is executing") hello = decorator(hello) hello()
Output:
What have we done here?
We have defined two functions here:
- The decorator: This is the decorator function that accepts another function as an argument and decorates aka modifies it before returning. Inside the decorator, we have defined a function called the wrapper Function. This is the function that performs the modification by wrapping the argument passed to it, function func. The decorator returns the wrapper function.
- The hello: This is the ordinary function that is to be decorated. We pass the hello function as an argument to the decorator. Consequently, hello now points to the wrapper function returned by the decorator.
However, the wrapper function has a reference to the original hello function as func, and calls that function between the two calls to print().
Syntactic Decorator
Do you know what syntactic sugar means in computer science lingo? It is syntax within a programming language that is designed to make the code easier to understand by humans. It makes things “sweeter” for our use, hence the name.
Python offers another way to use decorators by providing syntactic sugar with the @ symbol.
Syntax:
@decorator
def func(arg1, arg2, …):
pass
Consider the previous example but this time, with the syntactic decorator:
def decorator(func):
#The wrapper function inside #which the argument is called def wrapper(): print("This statement is printed before the function is called") func() print("This statement is printed after the function is called") return wrapper @decorator def hello(): print("Hello, the function is executing") hello()
Output:
Note how we are using @decorator instead of writing hello = decorator(hello).
Decorated Functions with Parameters
Suppose the function we need to decorate has some parameters. How are decorators used in such cases?
Let’s understand with the following example:
import functools
def decorator(func): @functools.wraps(func) def wrapper(): func() func() return wrapper @decorator def hello(x): print(f"Hello, {x}!") hello("World")
Output:
The above code returns a TypeError because the wrapper function has no arguments passed to it.
Now, we could pass one argument to the wrapper but then we will not be able to use the decorator with a function having more than one argument.
So instead, we will accept a varying number of arguments in the wrapper and pass those arguments to the original function func.
Look at the modified code below:
import functools
def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): func(*args, **kwargs) func(*args, **kwargs) return wrapper @decorator def hello(x): print(f"Hello, {x}!") hello("World")
Output:
What have we done here?
We have used *args and **kwargs to allow us to pass variable number of arguments or keyword arguments to a function.
Now, we pass ”World” as x to the hello function which is received by the wrapper function to call the actual function func. This is why Hello, World! is displayed twice.
Decorator Functions with Parameters
The decorator function itself can have parameters passed to it. This can be done by defining the decorator inside another function that accepts the parameters and then using those parameters inside the decorator. Note that you need to return the decorator from the enclosing function.
Let’s understand with the following example. In the previous example, we had created a decorator function that will be extended here to repeat any number of times. Let’s name the decorator function as repeat this time.
import functools
def repeat(num): def decorator_repeat(func): @functools.wraps(func) def wrapper(*args, **kwargs): for _ in range(num): val = func(*args, **kwargs) return val return wrapper return decorator_repeat @repeat(num=3) def hello(x): print(f"Hello, {x}!") hello("World")
Output:
What have we done here?
As you can see from the example above,
- The innermost function wrapper is taking a varying number of arguments using *args and **kwargs. It then calls the decorated function num number of times. The wrapper returns the return value of the original decorated function.
- Outside the wrapper function, one level above, we have the decorator_repeat function which works as a normal decorator that returns the wrapper function.
- At the outermost level, we have the decorator function called repeat that accepts a parameter and provides it to the inner functions using the closure pattern.
Note that, this time we have used the decorator repeat with a parenthesis ( ) to pass a parameter inside it.
Chaining Decorators in Python
Multiple decorators can be chained in Python. By chaining, we mean that one can apply multiple decorators to a single function. The chained decorators are also known as nesting decorators.
Let’s understand with the following example:
import functools
def stringSplit(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs).split() return wrapper def toLower(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs).lower() return wrapper
As you can see in the above, code, the first decorator takes a function that returns a string and splits it into a list of words. Then, the second decorator takes a function that returns a string and converts each character into lowercase.
Now, we will stack both the decorators and use them on a single function as shown below:
@stringSplit @toLower def hello(x): return f"Hello {x}" print(hello("World"))
Output:
Classes as Decorators
As mentioned above, even classes in Python can be used as decorators. There are two requirements to make a class as a decorator:
- The __init__ function should take a function as an argument.
- The class needs to implement the __call__ method (because a decorator must be a callable object, as discussed above).
Conclusion
In this article, we have discussed in detail about what are python decorator, why we need them and finally how to create python decorators.
Hope this article will help you in your data science journey.
This is a collection of insightful articles from domain experts in the fields of Cloud Computing, DevOps, AWS, Data Science, Machine Learning, AI, and Natural Language Processing. The range of topics caters to upski... Read Full Bio