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.
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:
- Decorators that accept function arguments
- 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.wrapsto 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.



