In This Article
In Python, functions are first-class objects, which means they can be assigned to variables, passed as arguments to other functions, and returned as values from other functions. This property enables the use of decorators in Python, which essentially wraps a function with another function, allowing you to perform additional actions before or after the wrapped function is called.
The syntax for using decorators involves using the “@” symbol followed by the name of the decorator function above the function definition. When the decorated function is called, the decorator function is invoked first, and it can modify the original function’s behavior or perform any other desired actions.
Python Decorators can be used for a variety of purposes, such as adding logging, input validation, authentication, caching, and more. They provide a clean and concise way to separate cross-cutting concerns from the core logic of a function or class. Python provides built-in decorators like “@property” and “@staticmethod” which are commonly used in object-oriented programming. However, decorators can also be defined by the user, allowing for custom functionality to be added to functions or classes.
Python Decorators with Examples
Python Decorators provide a clean and efficient way to add functionality, such as logging, caching, input validation, or authentication, to existing code.
Let’s explore some examples to understand how decorators work and how they can be applied.
- Logging Decorator: Suppose you have multiple functions in your codebase and want to add logging statements to track when each function is called and its return value.
Here’s an example of a logging decorator that achieves this:
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
@log_decorator
def multiply(a, b):
return a * b
# Function calls with logging
print(add(2, 3)) # Output: Calling add with args: (2, 3), kwargs: {}, add returned: 5
print(multiply(4, 5)) # Output: Calling multiply with args: (4, 5), kwargs: {}, multiply returned: 20
Here, the log_decorator
function takes a function as an argument, wraps it with additional logging statements, and returns the wrapped function. By applying the @log_decorator
above the function definitions, the functions add
and multiply
are automatically decorated with the logging behavior.
- Timing Decorator: Another common use case for decorators is measuring the execution time of a function. Here’s an example of a timing decorator:
import time
def time_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time} seconds to execute.")
return result
return wrapper
@time_decorator
def slow_function():
time.sleep(3)
return "Function executed."
# Function call with timing
print(slow_function()) # Output: slow_function took 3.001234 seconds to execute.
Here, the time_decorator
wraps the function slow_function
with timing logic. It measures the time taken to execute the function and prints the duration. The @time_decorator
notation applies the timing behavior to the slow_function
.
@ Symbol With Decorator in Python
In Python, the “@” symbol is used to apply a decorator to a function or class.
It is syntactic sugar that simplifies the process of decorating a target function or class with a decorator function.
When the “@” symbol is placed before the name of a decorator function and followed by the target function or class, it signifies that the decorator should be applied to the target.
The “@” symbol essentially serves as a shortcut for calling the decorator function and passing the target as an argument.
Example of @ Symbol With Decorator in Python-
def my_decorator(func):
def wrapper():
print("Before function execution")
func()
print("After function execution")
return wrapper
@my_decorator
def my_function():
print("Inside my_function")
# Calling the decorated function
my_function()
Here, the my_decorator
function is defined as a decorator. It takes a function as an argument, wraps it with additional functionality, and returns the wrapped function.
The decorator adds a “Before function execution” message before calling the target function and an “After function execution” message after executing the target.
By using the “@” symbol and applying the @my_decorator
above the my_function
definition, the decorator is automatically applied to the function. So, when my_function
is called, it is actually the decorated version that gets executed.
Output:
Before function execution
Inside my_function
After function execution
Decorating Functions with Parameters
Decorating functions with parameters in Python involves handling both the arguments passed to the original function and any additional arguments that might be needed by the decorator itself. This can be achieved by using *args
and **kwargs
to capture the arguments dynamically.
Example of decorating Functions with Parameters-
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before function execution")
result = func(*args, **kwargs)
print("After function execution")
return result
return wrapper
@my_decorator
def add_numbers(a, b):
return a + b
# Calling the decorated function with parameters
print(add_numbers(2, 3))
Here, the my_decorator
function is defined as a decorator.
The wrapper
function inside the decorator is designed to handle any number of arguments and keyword arguments.
It first executes the additional code before calling the target function and then executes the additional code after the target function is executed.
Finally, it returns the result of the target function. The *args
and **kwargs
parameters in the wrapper
function allows the decorator to capture and pass any number of arguments and keyword arguments to the target function, regardless of their names or values.
This ensures that the decorated function can still accept and process the necessary parameters.
Output:
Before function execution
After function execution
5
By using *args
and **kwargs
in the wrapper function, decorators can be applied to functions with any number and type of parameters, making the decoration process more flexible and accommodating different use cases.
It allows us to modify the behavior of functions while preserving their original parameter-handling functionality.
Chaining Decorators in Python
Chaining decorators in Python involves applying multiple decorators to a single function in a sequential manner.
This allows you to layer multiple functionalities onto a function by applying multiple decorators, each with its own specific behavior.
Chaining decorators can be done using the “@” symbol multiple times before the function definition.
Example Chaining Decorators in Python-
def uppercase_decorator(func):
def wrapper():
result = func().upper()
return result
return wrapper
def emphasis_decorator(func):
def wrapper():
result = func()
return f"**{result}**"
return wrapper
@uppercase_decorator
@emphasis_decorator
def greet():
return "hello"
# Calling the decorated function
print(greet())
Here, we have two decorators: uppercase_decorator
and emphasis_decorator
. The uppercase_decorator
converts the result of the function to uppercase, while the emphasis_decorator
wraps the result with double asterisks.
By using the “@” symbol twice before the function definition and applying the decorators in the order @uppercase_decorator
and @emphasis_decorator
, the decorators are chained together. This means that the output of the uppercase_decorator
becomes the input for the emphasis_decorator
.
When the greet
function is called, it executes the decorated version of the function. The execution starts with the emphasis_decorator
, which wraps the result of the uppercase_decorator
with double asterisks. Finally, the modified result is returned.
Output:
**HELLO**
Chaining decorators allow us to stack multiple layers of functionality onto a function, each modifying the behavior or result in a specific way. It provides a convenient and expressive way to combine and compose different decorators to achieve the desired functionality.
When chaining decorators, the order in which the decorators are applied is significant. The decorators closer to the function definition are applied first, followed by the decorators further away.
So, make sure to consider the order in which we chain the decorators to achieve the desired effect.