What Are Decorators in Python? Examples, Use, Syntax
Table of Contents
- Introduction
- What is Decorator in Python?
- Functions in Python
- Python Decorator Syntax
- Decorators in Python Example
- Python Decorator Symbol (@)
- Chaining Decorators in Python
- How to Reuse Decorators in Python?
- Fancy Decorators in Python
- When to Use Python Decorators?
- Where to Use Python Decorators?
- Why Write Decorators?
- Decorators with Parameters in Python
- Python Decorators FAQs
Introduction
Decorators in Python are design patterns that allow you to modify the functionalities of a function by wrapping one function into another. It accepts the original function and returns its modified version.
One thing to note is in Python, everything is an object, including functions. This blog will explain everything about a decorator in Python, along with a few important concepts.
What is Decorator in Python?
Decorators are useful and powerful tools in Python that allow programmers to modify the behaviour of functions. It takes the original function and returns it with some added functionalities.
Moreover, it provides flexibility to wrap one function into another to extend its behaviour without any permanent modifications. It is also known as metaprogramming, as a part of a program tries to change another section at a compile time.
Before we study Python decorators in detail, let’s understand a few concepts that will help you learn the topic better.
Functions in Python
Functions, one of the most interesting features of Python, are first-class citizens or objects. This means they can be passed as arguments, modified, returned from functions, and assigned to a variable. A function is treated as an object in Python, along with classes and any variables.
Here are some important things to know about Python functions:
-
It is possible to return a function from another function.
-
It is an instance of the object type.
-
A function can be passed as a parameter to a function.
-
We can store it in a variable.
-
We can store a function in data structures as lists and hash tables.
1. Assign Functions to Variables
We first create a function, add one to a number, assign the function to a variable, and use it to call the function.
2. Pass Functions as Arguments
A function can also be passed as arguments to another function.
3. Nested Function
When one function is included inside another, it is called a nested function. Python supports nested functions, so you can access the outer scope of the enclosing function. This important concept in Python decorators is called closure.
4. Decorator in Python
As explained above, decorators allow us to modify the behaviour of a function or class. It takes a function as an argument and wraps it inside another function.
Python Decorator Syntax
Decorators in Python are essentially functions that take another function as input, add some functionality to it, and return the modified function. They are denoted by the @ symbol followed by the decorator function name, placed directly above the function or class definition.
Here's the syntax for using decorators in Python:
@decorator_function
def function_name():
# Function body
@decorator_function
class ClassName:
# Class body
The decorator_function is the name of the decorator function you want to apply to the function or class. It can be any valid Python function that accepts a function or class as its argument and returns a modified function or class.
Decorators in Python Example
Here's an example of decorators in Python:
def uppercase_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def bold_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"<b>{result}</b>"
return wrapper
@bold_decorator
@uppercase_decorator
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
# Output: <b>HELLO, ALICE!</b>
Explanation:
In this example, we have two decorators: uppercase_decorator and bold_decorator. The uppercase_decorator converts the output of the decorated function to uppercase, while the bold_decorator wraps the output in HTML bold tags.
The greet function is decorated using both decorators by placing @bold_decorator above @uppercase_decorator. When greet("Alice") is called, the function execution follows the order of the decorators from top to bottom.
-
The uppercase_decorator modifies the output of greet("Alice") to "HELLO, ALICE!".
-
Then, the bold_decorator wraps the modified output in <b> and </b> tags, resulting in "<b>HELLO, ALICE!</b>".
-
Finally, the decorated output is printed.
You can modify the decorators in Python or add more decorators to suit your needs.
Python Decorator Symbol (@)
Rather than assigning a function call to a variable, Python provides an easier and more elegant way to attain this functionality. It uses @symbol and is called syntactic decorator or "pie" syntax.
Example:
Here's an example that shows the usage of the @ symbol as the decorator symbol:
def decorator_function(func):
def wrapper():
print("Before function execution")
func()
print("After function execution")
return wrapper
@decorator_function
def greet():
print("Hello, world!")
greet()
Explanation:
In this example, the @decorator_function syntax applies the decorator_function decorator to the greet() function. The decorator_function modifies the behavior of greet() by adding some additional functionality before and after the function execution.
When greet() is called, it will print:
Before function execution
Hello, world!
After function execution
The @ symbol simplifies the process of applying decorators and makes the code more readable by clearly indicating that a function or class is being decorated.
Chaining Decorators in Python
Python also allows chaining multiple decorators. This is known as chaining decorators. To apply multiple decorators to a function (or chain decorators in Python), we need to place them one after the other. The innermost decorator is applied first.
To chain decorators, you simply place multiple decorator functions one after another, each prefixed with the @ symbol, above the function or class definition. The order of the decorators is important, as the function execution follows the order from top to bottom.
Example:
Here's an example that shows how to chain decorators in Python:
def decorator1(func)
def wrapper():
print("Decorator 1")
func()
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper
@decorator1
@decorator2
def greet():
print("Hello, world!")
greet()
Explanation:
In this example, we have two decorators: decorator1 and decorator2. The greet() function is decorated using both decorators by chaining them using the @ symbol.
When greet() is called, the function execution follows the order of the decorators from top to bottom:
-
The decorator1 modifies the behavior of greet() by adding the message "Decorator 1" before executing the function.
-
The modified version of greet() is then passed to decorator2, which further modifies the behavior by adding the message "Decorator 2" before executing the function.
-
Finally, the modified greet() function is called, resulting in the following output:
Decorator 1
Decorator 2
Hello, world!
By chaining decorators in Python, you can create a pipeline of functionality that is applied in a specific order. This allows you to easily combine and reuse decorators to customize the behavior of your functions or classes.
How to Reuse Decorators in Python?
We can also reuse decorators in Python by recalling that particular decorator function. We can make a decorator with its own module, which can be used in various other functions.
In the following code, we have created a file called mod_decorator.py:
def do_twice(func):
def wrapper_do_twice():
func()
func()
return wrapper_do_twice
We can import mod_decorator.py in another file.
Fancy Decorators in Python
Fancy decorators in Python are the decorators that accept arguments or are implemented as classes rather than simple functions. These advanced decorators provide additional flexibility and functionality compared to regular decorators.
Let's explore two examples of fancy decorators: decorators with arguments and class-based decorators.
Decorators with Arguments:
Decorators with arguments allow you to customize the behavior of the decorator based on the provided arguments. To implement a decorator with arguments, you need an extra level of nested functions.
Class Decorators
Decorators are callable, accepting and returning a callable. Classes are callable too; decorators are used to decorating them. A few reasons for decorating classes are:
-
Attribute interaction
-
Attribute addition or enhancement
-
API alteration, which alters the way a class is declared and its instance use
To decorate a method inside a class, we can use built-in decorators, such as @staticmethod, @classmethod, and @property in Python. The @staticmethod and @classmethod are used to define methods in a class which is not connected to other instances of a class, while @property is used to modify the setters and getters of a class substitute.
Singleton Class
It has only one instance. Python has several singletons, such as None, True, etc.
Nesting Decorators
To use multiple decorators, you need to place them on top of each other.
Stateful Decorators
These decorators keep track of a decorator's state. For example, we can create a decorator to count the number of times the function has been called.
Classes as Decorators
We can use classes as decorators. We can create a class containing _init_() and treat func as an argument. As classes are callable, they can stand in for the decorated function. We can make a class callable by implementing the special _call_() method.
When to Use Python Decorators?
We use decorators in Python to change the behaviour of a function without changing the entire function. For example, when we want to test performance, add logging, verify permissions, perform caching, etc.
Another case to use decorators is when we need to run a single code on multiple functions without duplicating the code again and again.
Where to Use Python Decorators?
A standard Python library offers several modules with decorators, tools, and frameworks that make use of decorators in Python. Here are a few examples where decorators are used:
-
The mock module allows the use of @mock.patch or @mock.patch.object decorator used for unit testing.
-
The @classmethod or @staticmethod decorator is used to create a method without creating an instance.
-
Celery uses the @task decorator to determine a function as an asynchronous task.
-
Flask uses @app.route, and Django uses @login_required decorators for function registry and setting login privileges, respectively.
Why Write Decorators?
When written efficiently and properly, the reusable decorators in Python are explicit and modular.
-
Modularity of Decorators- Decorators allow programmers to add or remove functionalities conveniently in defined code blocks. This eliminates the need to repeat the boilerplate setup.
-
Decorators are Explicit- Another reason to use Python decorators is a decorator can be applied to callable according to the programmer’s requirements. Hence, enhancing code readability and making it cleaner.
Now, let’s take a look at some of the use cases of decorators to gain a better understanding of when to write decorators.
Functional Addons- The first and primary reason to write decorators is it provides flexibility to add more functionalities to the defined code blocks, i.e., functions and classes.
Data Sanitization- An efficient data sanitization method allows you to remove or destroy data stored in the memory and recover it easily. You can use the cache feature to avoid running the same function or use other methods to validate login credentials. These examples highlight the importance of data sanitization. A decorator sanitizes arguments passed to modified functions along with the data returned from the function.
Function Registration- Decorators allow several subsystems to interact with each other even without having many details about one another.
Decorators with Parameters in Python
Python Decorators are a useful and powerful tool as they enable programmers to modify the behaviour of a class or function. Functions in Python are First Class citizens, so they can be treated as objects.
The syntax for decorators with parameters:
@decorator(params)
def func_name():
''' Function implementation'''
This code is equal to:
def func_name():
''' Function implementation'''
func_name = (decorator(params))(func_name)’’’’
The execution starts from left to right, and the decorator (params) is called, which returns the function object func_obj. The func_obj (fun_name) is called using the func_obj. The required operations are performed in the inner function, and it returns the actual function reference, which is assigned to func_name. We will use func_name() to call the function with a decorator applied to it.
Python Decorators FAQs
Here are some frequently asked questions related to decorators in Python programming:
1. How to add arguments to decorators in Python?
We can pass arguments to decorators in Python. Add *args and **kwargs to the inner functions to add arguments to decorators. *args takes any number of arguments irrespective of its type, including True, 10, or ‘Brandon’, while **kwargs takes any number of keyword arguments, including is_authenticated=True, count=89, or name='Brandon'.
2. What are the building blocks to creating Python decorators?
To understand Python decorators and master the topic, you need to first learn about a few crucial concepts.
-
You can nest a function inside another function.
-
As it is possible to nest one function inside another, it can be returned as well.
-
A function represents an object, so it can be assigned to a variable. You can access a function through the variable.
-
You can pass a function to another function as an argument.
3. How to create a Python decorator?
To create a Python decorator, start by creating an outer function that takes a function as an argument. There will be an inner function, too, that will wrap around the decorated function.
Conclusion
Decorators in Python are an important concept and a powerful tool to wrap code around classes and functions. This is an efficient way to boilerplate code and enhances code readability. Despite the numerous benefits it offers, decorators have a few drawbacks. As it is used to wrap code around defined blocks, debugging it can be tricky and challenging. Moreover, inefficiently written decorators can lead to errors.