Decorators in Python are a feature that allows you to modify or extend the behavior of a function or class without changing its original code. A decorator takes a function as an argument, adds some additional functionality, and returns a modified function. Decorators are commonly used for logging, authentication, validation, timing, and other reusable tasks.
Table of Contents
What are Decorators in Python?
A decorator is a function that takes another function as an argument and extends or modifies its behavior without changing the original function.
Python functions are first-class objects, which means they can be passed as arguments to other functions and returned from functions.
The basic syntax for applying a decorator is:
@decorator_name
Why Use Decorators in Python?
Decorators are useful when you want to add common functionality to multiple functions without changing their original code.
- Encourage Code Reuse: Decorators allow the same functionality to be applied to multiple functions without writing the same code repeatedly.
- Reduce Repetitive Code: They help avoid repeating common tasks, such as logging, validation, or timing code across different functions.
- Modify Function Behavior: Decorators can add or modify a function's behavior without changing the function's original implementation.
- Support Logging and Debugging: Decorators can be used to log function calls or gather information that helps while debugging a program.
- Handle Authentication and Authorization: Decorators can check whether a user is authenticated or has the required permissions before allowing a function to run.
- Perform Input Validation: Decorators can validate function arguments before the function executes.
- Measure Execution Time: Decorators can measure how long a function takes to execute, which can be useful when analyzing program performance.
How Do Decorators Work?
A decorator works by taking a function, adding some extra behavior to it, and then returning a new function that includes that behavior.
- Pass the Function to the Decorator: The original function is passed to the decorator so that the decorator can work with it.
- Define a Wrapper Function: The decorator defines a wrapper function that controls how and when the original function is called.
- Add Additional Functionality: The wrapper function adds the extra functionality that you want to apply to the original function.
- Call the Original Function: The wrapper function calls the original function as part of its execution.
- Return the Wrapper Function: The decorator returns the wrapper function, which can then be used in place of the original function.
# Python program to demonstrate how a decorator works
def my_decorator(function):
def wrapper():
print("Before function execution")
function()
print("After function execution")
return wrapper
def greet():
print("Hello, Python!")
greet = my_decorator(greet)
greet()
Output:
Before function execution
Hello, Python!
After function execution
Creating a Simple Decorator
A simple decorator can be created using a wrapper function.
# Python program to create a simple decorator
def message_decorator(function):
def wrapper():
print("Function is starting")
function()
print("Function is completed")
return wrapper
def greet():
print("Hello, Python!")
greet = message_decorator(greet)
greet()
Output:
Function is starting
Hello, Python!
Function is completed
Here, message_decorator() accepts the greet() function and returns the wrapper() function.
Applying a Decorator Using @
Python provides the @ symbol as a convenient way to apply a decorator.
# Python program to apply a decorator using the @ syntax
def message_decorator(function):
def wrapper():
print("Function is starting")
function()
print("Function is completed")
return wrapper
@message_decorator
def greet():
print("Hello, Python!")
greet()
Output:
Function is starting
Hello, Python!
Function is completed
The following two statements are equivalent:
greet = message_decorator(greet)
@message_decorator
The @ syntax makes the code shorter and easier to read.
Decorators with Arguments
A decorator can also be used with functions that accept arguments. The wrapper function needs to accept the required arguments.
# Python program to use a decorator with function arguments
def greeting_decorator(function):
def wrapper(name):
print("Greeting function called")
function(name)
return wrapper
@greeting_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Tanavi")
Output:
Greeting function called
Hello, Tanavi!
Here, the name argument is received by the wrapper and passed to the original function.
Returning Values from Decorated Functions
If the original function returns a value, the wrapper should return that value as well.
# Python program to return a value from a decorated function
def calculation_decorator(function):
def wrapper(a, b):
result = function(a, b)
return result
return wrapper
@calculation_decorator
def add(a, b):
return a + b
result = add(10, 20)
print(result)
Output:
30
The wrapper stores the result returned by add() and then returns it to the caller.
Decorators with *args and **kwargs
Using *args and **kwargs allows a decorator to work with functions that accept different numbers of positional and keyword arguments.
# Python program to use *args and **kwargs with a decorator
def log_decorator(function):
def wrapper(*args, **kwargs):
print("Function called")
return function(*args, **kwargs)
return wrapper
@log_decorator
def greet(name, message="Hello"):
print(f"{message}, {name}!")
greet("Tanavi", message="Welcome")
Output:
Function called
Welcome, Tanavi!
The wrapper() can accept different types and numbers of arguments because of *args and **kwargs.
Using Multiple Decorators
More than one decorator can be applied to the same function.
# Python program to use multiple decorators
def first_decorator(function):
def wrapper():
print("First decorator")
function()
return wrapper
def second_decorator(function):
def wrapper():
print("Second decorator")
function()
return wrapper
@first_decorator
@second_decorator
def greet():
print("Hello!")
greet()
Output:
First decorator
Second decorator
Hello!
When multiple decorators are applied, the decorator closest to the function is applied first.
Preserving Function Metadata Using functools. wraps
A decorated function can lose metadata such as its original name and documentation because the wrapper replaces the original function.
The wraps() decorator from the functools module can preserve this information.
# Python program to preserve function metadata using functools.wraps
from functools import wraps
def log_decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Function called")
return function(*args, **kwargs)
return wrapper
@log_decorator
def greet():
"""This function displays a greeting."""
print("Hello!")
greet()
print(greet.__name__)
print(greet.__doc__)
Output:
Function called
Hello!
greet
This function displays a greeting.
functools. wraps helps preserve metadata from the original function.
Advantages of Decorators
- Code Reusability: The same decorator can be applied to multiple functions.
- Less Repetition: Common functionality can be written once and reused.
- Separation of Concerns: Additional functionality can be separated from the main function logic.
- Easy Maintenance: Changes to common functionality can be made inside the decorator.
- Flexible: Decorators can work with different functions using *args and **kwargs.
- Useful in Applications: Decorators are commonly used for logging, authentication, validation, caching, and performance measurement.
Common Beginner Mistakes
- Forgetting to Return the Wrapper: A decorator should return the wrapper function so that the decorated function continues to work.
- Not Returning the Original Result: If the original function returns a value, the wrapper should normally return that value.
- Not Handling Function Arguments: A wrapper without parameters cannot directly handle functions that require arguments. Using *args and **kwargs makes the decorator more flexible.'
- Forgetting functools. wraps: Without functools. wraps, useful metadata such as the original function name and documentation may not be preserved.
- Confusing a Decorator with a Decorated Function: A decorator modifies or extends a function, while the decorated function is the function after the decorator has been applied.
Conclusion
Decorators in Python provide a convenient way to add or modify functionality without changing the original function code. They work by accepting a function, adding additional behavior through a wrapper, and returning the modified function. The @ syntax makes decorators easy to apply, while *args, **kwargs, and functools.wraps make them more flexible and reliable. Decorators are widely used in Python applications for tasks such as logging, authentication, validation, caching, and performance measurement.
Frequently Asked Questions
1. What is a decorator in Python?
A decorator is a function that accepts another function and modifies or extends its behavior without changing its original code.
2. Which symbol is used to apply a decorator?
The @ symbol is used to apply a decorator to a function or class.
3. What is a wrapper function?
A wrapper function is an inner function used by a decorator to add additional behavior around the original function.
4. Can decorators work with functions that have arguments?
Yes. Decorators can use *args and **kwargs to support functions with different types and numbers of arguments.
5. What is the purpose of functools. wraps?
functools.wraps preserves metadata such as the original function's name and documentation when a decorator is applied.
0 Comments