Decorator Parameters and Return Value

Introduction to Decorators in Python

Decorators are a powerful feature in Python that allow developers to modify or enhance existing functions without changing their implementation. They are essentially functions that take another function as input and return a new function. This pattern enables code reuse, encapsulation, and the addition of functionality to existing code. Decorators are widely used in Python for tasks such as logging, validation, and performance monitoring.

Parameters in Decorators

Decorators can accept parameters, which are passed to the decorated function. When a decorator is applied, its parameters are passed to the wrapper function, which then uses them to modify the original function. For example, a decorator might take a parameter to control its behavior, such as a timeout or a logging level.

The parameters of a decorator are typically passed as arguments to the wrapper function. If a decorator has multiple parameters, they can be passed in any order, and the wrapper function can access them using *args or **kwargs. For instance:

def my_decorator(param1, param2):
    def wrapper(func):
        def new_func(*args, **kwargs):
            # Access parameters
            return func(param1, param2, *args, **kwargs)
        return new_func
    return wrapper

@my_decorator(10, 20)
def greet(name):
    return f"Hello {name}"

In this example, param1 and param2 are passed to the my_decorator function and then used in the greet function. The wrapper function captures these parameters and passes them to the original function.

Return Values in Decorators

Decorators can also return values, which are then used as the decorated function. This allows for flexible control over the behavior of the decorated function. For example, a decorator might return a modified function or a value that overrides the original function's output.

A decorator can return a function or a value that replaces the original function. For instance:

def my_decorator(param1):
    def wrapper(func):
        def new_func(*args, **kwargs):
            # Return a modified function or value
            return func(param1, *args, **kwargs)
        return new_func
    return wrapper

@my_decorator(20)
def add(a, b):
    return a + b

In this case, the my_decorator returns a wrapper function that modifies the add function. The new_func uses param1 to adjust the behavior of the original function.

Practical Examples

Example 1: Passing Parameters to a Decorator

A decorator can accept multiple parameters and pass them to the wrapped function. This is useful for customizing the behavior of a function based on external inputs.

def with_logging(level, message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # Log the message based on the level
            if level == "INFO":
                print(f"INFO: {message} {args, kwargs}")
            elif level == "ERROR":
                print(f"ERROR: {message} {args, kwargs}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@with_logging("INFO", "Function called")
def say_hello(name):
    return f"Hello {name}"

say_hello("Alice")

This example demonstrates how a decorator can accept parameters (level and message) and use them to log the function's execution.

Example 2: Returning a Value from a Decorator

A decorator can return a value that replaces the original function. This is useful for creating wrappers that modify the function's output.

def return_value_decorator(value):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # Return the specified value
            return value
        return wrapper
    return decorator

@return_value_decorator(42)
def square(a):
    return a * a

print(square(3))

Here, the return_value_decorator returns a fixed value (42) instead of the result of the square function. The wrapper function simply returns the value, overriding the original function's behavior.

Conclusion

Decorators in Python offer flexibility by allowing parameters to be passed to the wrapped function and return values to be used in place of the original function. Understanding how to handle parameters and return values in decorators is essential for creating reusable and maintainable code. By leveraging decorators, developers can enhance existing functions without altering their implementation, leading to cleaner and more efficient code.