How to implement parameterized decorators

PythonPythonBeginner
Practice Now

Introduction

Python decorators are powerful tools for modifying and enhancing function behavior. This tutorial explores the advanced technique of creating parameterized decorators, which allow developers to dynamically configure function wrappers with flexible arguments and complex transformation logic.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/FunctionsGroup -.-> python/scope("Scope") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/function_definition -.-> lab-438212{{"How to implement parameterized decorators"}} python/arguments_return -.-> lab-438212{{"How to implement parameterized decorators"}} python/lambda_functions -.-> lab-438212{{"How to implement parameterized decorators"}} python/scope -.-> lab-438212{{"How to implement parameterized decorators"}} python/decorators -.-> lab-438212{{"How to implement parameterized decorators"}} end

Decorator Basics

What is a Decorator?

In Python, a decorator is a powerful design pattern that allows you to modify or enhance functions and methods without directly changing their source code. Essentially, decorators are functions that take another function as an argument and return a modified version of that function.

Basic Decorator Syntax

def simple_decorator(func):
    def wrapper():
        print("Something before the function is called.")
        func()
        print("Something after the function is called.")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello, LabEx!")

say_hello()

Key Characteristics of Decorators

Characteristic Description
Function Modification Allows dynamic modification of function behavior
Non-invasive Doesn't change the original function's source code
Reusability Can be applied to multiple functions

How Decorators Work

graph LR A[Original Function] --> B[Decorator Function] B --> C[Wrapped Function] C --> D[Enhanced Functionality]

Types of Decorators

  1. Function Decorators: Modify individual functions
  2. Class Decorators: Modify entire classes
  3. Method Decorators: Modify class methods

Practical Example

def timer_decorator(func):
    import time
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds")
        return result
    return wrapper

@timer_decorator
def complex_calculation():
    return sum(range(1000000))

complex_calculation()

Common Use Cases

  • Logging
  • Performance measurement
  • Authentication
  • Caching
  • Input validation

Best Practices

  • Keep decorators simple and focused
  • Use functools.wraps to preserve original function metadata
  • Understand the performance implications of decorators

By understanding these basics, developers can leverage decorators to write more modular and efficient Python code with LabEx's recommended approach to clean, maintainable programming.

Parameterized Decorator Design

Understanding Parameterized Decorators

Parameterized decorators are advanced decorator functions that can accept arguments, allowing for more flexible and configurable function modifications.

Basic Structure of Parameterized Decorators

def decorator_with_args(arg1, arg2):
    def actual_decorator(func):
        def wrapper(*args, **kwargs):
            ## Decorator logic using arg1 and arg2
            print(f"Decorator arguments: {arg1}, {arg2}")
            return func(*args, **kwargs)
        return wrapper
    return actual_decorator

Decorator Argument Patterns

Pattern Description Use Case
Configuration Modify decorator behavior Logging levels
Validation Add input checks Parameter constraints
Transformation Modify function inputs/outputs Data preprocessing

Practical Implementation Example

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("LabEx")

Decorator Composition Flow

graph LR A[Decorator Arguments] --> B[Decorator Factory] B --> C[Actual Decorator] C --> D[Wrapped Function] D --> E[Enhanced Functionality]

Advanced Parameterized Decorator Techniques

Conditional Decoration

def conditional_decorator(condition):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if condition:
                print("Condition met, applying decorator")
                return func(*args, **kwargs)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@conditional_decorator(condition=True)
def example_function():
    print("Function executed")

Common Use Cases

  1. Logging with configurable verbosity
  2. Caching with custom expiration
  3. Rate limiting
  4. Performance monitoring
  5. Input validation

Best Practices

  • Keep decorator logic minimal
  • Use functools.wraps to preserve function metadata
  • Ensure type safety and error handling
  • Consider performance implications

Error Handling in Parameterized Decorators

def validate_type(expected_type):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for arg in args:
                if not isinstance(arg, expected_type):
                    raise TypeError(f"Expected {expected_type}, got {type(arg)}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_type(int)
def add_numbers(a, b):
    return a + b

By mastering parameterized decorators, developers can create more dynamic and flexible Python code with LabEx's advanced programming techniques.

Real-world Applications

Performance Monitoring Decorator

import time
import functools

def performance_monitor(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} execution time: {end_time - start_time} seconds")
        return result
    return wrapper

@performance_monitor
def complex_calculation(n):
    return sum(range(n))

complex_calculation(1000000)

Authentication and Authorization Decorator

def require_auth(roles):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.role in roles:
                return func(user, *args, **kwargs)
            raise PermissionError("Unauthorized access")
        return wrapper
    return decorator

class User:
    def __init__(self, name, role):
        self.name = name
        self.role = role

@require_auth(['admin', 'manager'])
def delete_user(user, user_id):
    print(f"User {user_id} deleted by {user.name}")

admin = User("Alice", "admin")
delete_user(admin, 123)

Caching Decorator with Expiration

from functools import wraps
import time

def cache_with_expiry(expiry_seconds):
    def decorator(func):
        cache = {}
        def wrapper(*args, **kwargs):
            key = str(args) + str(kwargs)
            current_time = time.time()

            if key in cache:
                result, timestamp = cache[key]
                if current_time - timestamp < expiry_seconds:
                    return result

            result = func(*args, **kwargs)
            cache[key] = (result, current_time)
            return result
        return wrapper
    return decorator

@cache_with_expiry(expiry_seconds=5)
def expensive_computation(x, y):
    time.sleep(2)
    return x + y

Decorator Application Scenarios

Scenario Decorator Purpose Key Benefits
Logging Track function calls Audit trails
Retry Mechanism Handle transient failures Resilience
Input Validation Ensure data integrity Error prevention
Rate Limiting Control API usage Resource management

Retry Mechanism Decorator

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unreliable_network_call():
    ## Simulated network request
    import random
    if random.random() < 0.7:
        raise ConnectionError("Network error")
    return "Success"

Decorator Workflow

graph LR A[Function Call] --> B{Decorator Check} B --> |Pass| C[Original Function] B --> |Fail| D[Error Handling] C --> E[Return Result] D --> F[Alternative Action]

Advanced Logging Decorator

import logging
import functools

def log_calls(log_level=logging.INFO):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            logging.log(log_level, f"Calling {func.__name__}")
            try:
                result = func(*args, **kwargs)
                logging.log(log_level, f"{func.__name__} completed successfully")
                return result
            except Exception as e:
                logging.error(f"{func.__name__} raised {type(e).__name__}: {e}")
                raise
        return wrapper
    return decorator

@log_calls(log_level=logging.DEBUG)
def process_data(data):
    ## Data processing logic
    pass

By exploring these real-world applications, developers can leverage decorators to create more robust, maintainable code with LabEx's advanced programming techniques.

Summary

By mastering parameterized decorators in Python, developers can create more modular, reusable, and adaptable code. These advanced techniques enable sophisticated function manipulation, providing elegant solutions for cross-cutting concerns like logging, authentication, and performance monitoring across different programming scenarios.