Python decorators are robust and flexible features that enable you to modify the behaviors of the methods, functions, and classes without changing their source code.
They offer a clean way of adding functionality to the existing code by wrapping it with additional logic.
In Today’s guide, we will discuss Python decorators, their working, usage, the method to use decorators with arguments, chaining and stacking decorators, and the creation of custom decorators. So, let’s get started!
What are Decorators in Python
As a Python programmer, decorators permit you to improve or change the behavior of methods, and functions, without changing their actual code.
You can utilize >decorators in different scenarios, such as input validation, logging, and code profiling.
Additionally, decorators use functions as their first-class objects and provide an efficient way for adding functionality to existing functions.
How Does Decorator Work in Python
Talking about working, a decorator is a function that accepts another function as an argument. It extends its behavior without altering it explicitly. This can be achieved by using the “@decorator_name
” syntax above the function that needs to be decorated.
When the decorated function is invoked, it is actually replaced by the decorator function. More specifically, this concept is known as function wrapping.
For instance, we will first define a simple decorator “my_decorator()
” that adds some behavior before and after the decorated function “say_hello()
” is invoked.
Here, the “@my_decorator
” syntax applies to the decorator the “say_hello()
” function.
When we invoke the “say_hello()
” function, it is wrapped by the “my_decorator()
” function which will ultimately print the additional output.
def my_decorator(func): def wrapper(): print("Something is happening before the function is invoked.") func() print("Something is happening after the function is invoked.") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello()
How to Create and Apply Decorators in Python
As discussed above, the “@decorator_name
” syntax can be added above the function definition. In this way, you can also chain decorators, which permits multiple layers of functionality to be added.
In the below example, we will create a decorator named “uppercase_decorator()
” that transforms the result of the “say_hello()
” decorated function into uppercase.
When we will invoke the “say_hello()
” method with the “User
” as an argument. Then, the decorator updates the output string in the uppercase.
def uppercase_decorator(func): def wrapper(text): result = func(text) return result.upper() return wrapper @uppercase_decorator def say_hello(name): return f"Hello, {name}!" print(say_hello("User"))
Using Python Decorators
Python decorators
can be utilized for logging function calls, caching results, measuring execution time, access control, and more. They offer a modular way of improving the functionality of the function without miss-arranging the relevant code.
Here, we will define a “log_decorator()
” function that logs the function calls and outputs their values. When this log function will be applied to the “add()
” function with the “@
” syntax.
def log_decorator(func): def wrapper(*args, **kwargs): print(f"Calling function: {func.__name__}") result = func(*args, **kwargs) print(f"{func.__name__} returned: {result}") return result return wrapper @log_decorator def add(a, b): return a + b result = add(3, 5)
The decorator
logs information before and after the function execution which ultimately provides insights into the behavior of the function.
Python Decorators with Arguments
Decorators
can accept arguments that permit them to be more customizable and flexible. This enables the decorators to adapt their behavior based on the input arguments.
According to the provided code, the “repeat()
” decorator accepts an argument “n
” and creates a decorator that repeats the decorated function’s execution “n
” times.
More specifically, passing “3
” as the “@repeat()
” decorator function argument applies the decorator to the “greet()
” function.
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(name): print(f"Hello, {name}!") greet("User")
Consequently, the specified greeting will be displayed three times on the console.
Chaining and Stacking Decorators
Decorators
can be combined by stacking them by utilizing the “@
” syntax. This chaining permits multiple decorators to alter the behavior of a function in a sequential manner.
For instance, we have two decorates named “upper_decorator()
” and “exclamation_decorator()
“. So when these decorators are applied to the “say_hello()
” function in the given sequence, the decorators will modify the behavior of the function.
def uppercase_decorator(func): def wrapper(text): result = func(text) return result.upper() return wrapper def exclamation_decorator(func): def wrapper(text): result = func(text) return f"{result}!" return wrapper @exclamation_decorator @uppercase_decorator def say_hello(name): return f"Hello, {name}." greeting = say_hello("GeeksVeda User") print(greeting)
It can be observed that the greeting message has been shown in uppercase with an exclamation mark.
Decorators for Class Methods and Properties
Decorators
are not only restricted to functions. They can be also utilized with the class methods and properties. This enables you to alter the behavior of the properties and methods within the classes. Additionally, this offers additional control and customization.
Here, the “upper_property()
” decorator has been applied to the “full_name
” property of the “Person
” class. This decorator then converts the property’s value to uppercase when accessed.
def uppercase_property(func): return property(lambda self: func(self).upper()) class Person: def __init__(self, first_name, last_name): self._first_name = first_name self._last_name = last_name @uppercase_property def full_name(self): return f"{self._first_name} {self._last_name}" person = Person("Sharqa", "Hameed") print(person.full_name)
How to Create Custom Decorators
In Python, you are also permitted to create customized decorators for addressing specific requirements or implementing reusable behavior. They offer a way for encapsulating logic that can be applied to multiple functions, classes, or methods.
For instance, we will define a “captilize_decorator()
” that capitalizes the first letter of the resultant values returned by a function. This custom decorator will be then applied to both the “greet()
” and “introduce()
” functions.
def capitalize_decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.capitalize() return wrapper @capitalize_decorator def greet(name): return f"hello, {name}" @capitalize_decorator def introduce(name, age): return f"my name is {name} and I am {age} years old" greeting = greet("GeeksVeda User") print(greeting) intro = introduce("Sharqa Hameed", 25) print(intro)
That’s all from this effective guide related to Python decorators.
Conclusion
In Python programming, decorators enable you to create reusable and modular code. You can utilize it to improve the readability of your code. More specifically, Python decorators allow you to create and enhance the code functionality and also maintain simplicity.
Therefore, in this guide, we have explained this concept in detail.
Want to explore and learn more related to Python, do check out our dedicated Python Tutorial Series!