How to handle decorator with multiple arguments

PythonPythonBeginner
Practice Now

Introduction

Python decorators are powerful tools that enable developers to modify or enhance functions without directly changing their source code. This tutorial explores advanced decorator techniques for handling multiple arguments, providing developers with comprehensive strategies to create more flexible and dynamic function wrappers.


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-419675{{"`How to handle decorator with multiple arguments`"}} python/arguments_return -.-> lab-419675{{"`How to handle decorator with multiple arguments`"}} python/lambda_functions -.-> lab-419675{{"`How to handle decorator with multiple arguments`"}} python/scope -.-> lab-419675{{"`How to handle decorator with multiple arguments`"}} python/decorators -.-> lab-419675{{"`How to handle decorator with multiple arguments`"}} end

Decorator Basics

What is a Decorator?

In Python, a decorator is a powerful and elegant way to modify or enhance functions or classes without directly changing their source code. It allows you to wrap a function with another function, adding extra functionality dynamically.

Basic Decorator Syntax

A basic decorator follows this fundamental structure:

def my_decorator(func):
    def wrapper():
        ## Code to execute before the original function
        func()  ## Call the original function
        ## Code to execute after the original function
    return wrapper

@my_decorator
def original_function():
    print("Original function is running")

Simple Decorator Example

Let's demonstrate a simple performance timing decorator:

import time

def timer_decorator(func):
    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 slow_function():
    time.sleep(2)
    print("Slow function completed")

slow_function()

Decorator Workflow

graph TD A[Original Function] --> B[Decorator Wrapper] B --> C{Pre-processing} C --> D[Call Original Function] D --> E{Post-processing} E --> F[Return Result]

Key Characteristics of Decorators

Characteristic Description
Reusability Can be applied to multiple functions
Non-invasive Doesn't modify original function code
Extensibility Can add multiple layers of functionality

Common Use Cases

Decorators are commonly used for:

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

By understanding these basics, you're ready to explore more advanced decorator techniques in LabEx Python programming courses.

Decorators with Arguments

Understanding Decorator Arguments

Decorators can accept arguments to provide more flexibility and customization. There are two primary ways to create decorators with arguments:

  1. Decorators that accept function arguments
  2. Decorators that accept their own arguments

Decorator Accepting Function Arguments

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

@validate_args
def add_numbers(a, b):
    return a + b

print(add_numbers(3, 4))  ## Works fine
## print(add_numbers(3, "4"))  ## Raises TypeError

Decorator with Custom Arguments

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 Argument Flow

graph TD A[Decorator Arguments] --> B[Decorator Factory] B --> C[Decorator Wrapper] C --> D[Original Function] D --> E[Return Result]

Types of Decorator Arguments

Argument Type Description Example
Positional Fixed position arguments @repeat(3)
Keyword Named arguments @validate(min_length=5)
Mixed Combination of positional and keyword @custom_decorator(10, strict=True)

Advanced Argument Handling

def log_calls(log_level='INFO'):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{log_level}] Calling {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_calls(log_level='DEBUG')
def complex_calculation(x, y):
    return x * y

complex_calculation(5, 6)

Best Practices

  • Use nested functions for complex argument processing
  • Preserve original function metadata using functools.wraps
  • Keep decorator logic clean and focused
  • Handle different argument scenarios gracefully

By mastering decorators with arguments, you'll unlock powerful metaprogramming techniques in Python, enhancing your code's flexibility and reusability in LabEx programming projects.

Practical Decorator Examples

Caching Decorator

from functools import lru_cache

@lru_cache(maxsize=100)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  ## Extremely fast computation

Authentication Decorator

def authenticate(role='user'):
    def decorator(func):
        def wrapper(*args, **kwargs):
            user_role = 'admin'  ## Simulated authentication
            if user_role == role:
                return func(*args, **kwargs)
            else:
                raise PermissionError("Unauthorized access")
        return wrapper
    return decorator

@authenticate(role='admin')
def delete_system_files():
    print("System files deleted")

delete_system_files()

Performance Monitoring Decorator

import time
import functools

def performance_monitor(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} executed in {end - start:.4f} seconds")
        return result
    return wrapper

@performance_monitor
def complex_computation(n):
    return sum(i**2 for i in range(n))

complex_computation(1000000)

Decorator Workflow

graph TD A[Decorator] --> B[Input Validation] B --> C[Performance Tracking] C --> D[Authentication] D --> E[Caching] E --> F[Original Function]

Real-world Decorator Applications

Use Case Description Example
Logging Track function calls Capture method invocations
Rate Limiting Control function execution frequency API request management
Input Validation Ensure data integrity Type and range checking
Retry Mechanism Automatic error recovery Network request handling

Retry Decorator with Exponential Backoff

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @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
                    wait = delay * (2 ** attempts)
                    time.sleep(wait)
        return wrapper
    return decorator

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

unstable_network_request()

Advanced Decorator Techniques

  • Combine multiple decorators
  • Create class-based decorators
  • Use functools.wraps to preserve metadata
  • Handle complex argument scenarios

By exploring these practical examples, you'll gain insights into the versatility of decorators in LabEx Python programming, enabling more robust and efficient code design.

Summary

By mastering decorators with multiple arguments, Python developers can create more sophisticated and reusable code structures. This tutorial demonstrates how to implement complex decorators that can accept various argument types, enhance function behavior, and improve overall code modularity and maintainability.

Other Python Tutorials you may like