While function-based decorators are widely used, class-based decorators can offer more flexibility and better organization, especially when the decorator needs to maintain state or requires multiple methods to function properly. A Python class decorator is simply a class that implements the __call__ method, allowing an instance of the class to be used as a decorator.
This article explores the concept of using classes as decorators in Python, illustrating their purpose, implementation, and benefits through practical examples.
Implementing Class Decorators
Example: Let's consider a simple example where we use a class decorator to log the execution time of a function:
import time
class TimerDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
print(f"Function {self.func.__name__} executed in {end_time - start_time} seconds")
return result
@TimerDecorator
def example_function(n):
total = 0
for i in range(n):
total += i
return total
# Usage
print(example_function(1000000))
Output
Function example_function executed in 0.06820821762084961 seconds 499999500000
Class Decorator with *args and **kwargs
In this example, the class decorator demonstrates how you can modify the return value of a function, adding additional logic to its execution.
Example:
- LoggerDecorator Class:
- The
__init__method takes the function to be decorated and stores it. - The
__call__method logs the arguments and keyword arguments, calls the original function with these arguments, and returns the result.
- The
- greet Function:
- This is a simple function that takes a name and an optional greeting message.
- Using the Decorator:
- When
greetis called, theLoggerDecoratorlogs the arguments before executing the function.
- When
class LoggerDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Arguments: {args}, Keyword Arguments: {kwargs}")
result = self.func(*args, **kwargs)
return result
@LoggerDecorator
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Usage
print(greet("Alice"))
print(greet("Bob", greeting="Hi"))
Output
Arguments: ('Alice',), Keyword Arguments: {}
Hello, Alice!
Arguments: ('Bob',), Keyword Arguments: {'greeting': 'Hi'}
Hi, Bob!
Class Decorator with return Statement
In this example, the class decorator demonstrates how you can modify the return value of a function, adding additional logic to its execution.
Example:
- DoubleReturnDecorator Class:
- The
__init__method takes the function to be decorated and stores it. - The
__call__method calls the original function with the provided arguments, then doubles the result and returns it.
- The
- add Function:
- This is a simple function that adds two numbers.
- Using the Decorator:
- When
addis called, theDoubleReturnDecoratormodifies the return value by doubling it before returning it.
- When
class DoubleReturnDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
result = self.func(*args, **kwargs)
return result * 2
@DoubleReturnDecorator
def add(a, b):
return a + b
# Usage
print(add(3, 5))
print(add(10, 20))
Output
16 60
Checking for an Invalid Parameter with a Class Decorator
In this example, the class decorator demonstrates how you can check for specific parameter values and handle errors appropriately by raising exceptions when conditions are not met.
Example
- ErrorCheckDecorator Class:
- The
__init__method takes the function to be decorated and stores it. - The
__call__method checks if theerrorkeyword argument is set toTrue. If it is, it raises aValueError. Otherwise, it calls the original function with the provided arguments.
- The
- process_data Function:
- This is a simple function that takes some data and an optional
errorflag.
- This is a simple function that takes some data and an optional
- Using the Decorator:
- When
process_datais called witherror=True, the decorator raises aValueError. Otherwise, it processes the data normally.
- When
class ErrorCheckDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
if kwargs.get('error', False):
raise ValueError("Invalid parameter 'error' set to True")
return self.func(*args, **kwargs)
@ErrorCheckDecorator
def process_data(data, error=False):
return f"Processing data: {data}"
# Usage
try:
print(process_data("sample_data"))
print(process_data("sample_data", error=True)) # This will raise ValueError
except ValueError as e:
print(e)
Output
Processing data: sample_data Invalid parameter 'error' set to True