Stacking Decorators: Yes, You Can!

Stacking Decorators: Yes, You Can!

Imagine this: You’ve just written a Python function, and you want to add some extra behavior—maybe log its execution time, validate its inputs, and cache its results. Do you rewrite the function three times? Nope! Instead, you stack decorators like fluffy pancakes 🥞, layering them one on top of the other.

Decorators are one of Python’s most elegant features, allowing you to modify or extend functions without changing their core logic. But here’s the fun part: you can use multiple decorators on a single function, combining their powers like a coding superhero. Let’s break it down.


How Decorator Stacking Works

Decorators are applied from the bottom up. Think of them as wrapping paper: the bottom decorator wraps the function first, then the next one wraps that result, and so on.

Here’s a simple example:

@log  
@timer  
def my_function():  
    # Do something  
    pass  

This is equivalent to:

my_function = log(timer(my_function))  

The order matters! If you reverse them (@timer @log), the behavior changes.


Why Stack Decorators?

Stacking decorators lets you:

Keep code clean – Avoid cluttering your function with extra logic.
Reuse decorators – Apply the same decorator to multiple functions.
Combine functionalities – Mix logging, timing, caching, and more.


Common Decorator Combinations

Here are some powerful pairs (or trios!) you can experiment with:

1. Logging + Timing

Track how long a function runs and log its activity.

def log(func):  
    def wrapper(*args, **kwargs):  
        print(f"Calling {func.__name__}")  
        return func(*args, **kwargs)  
    return wrapper  

def timer(func):  
    import time  
    def wrapper(*args, **kwargs):  
        start = time.time()  
        result = func(*args, **kwargs)  
        end = time.time()  
        print(f"{func.__name__} took {end - start:.2f}s")  
        return result  
    return wrapper  

@log  
@timer  
def process_data():  
    # Heavy computation here  
    pass  

2. Validation + Retry

Check inputs and retry on failure.

def validate_input(func):  
    def wrapper(x):  
        if x < 0:  
            raise ValueError("Positive numbers only!")  
        return func(x)  
    return wrapper  

def retry(max_attempts=3):  
    def decorator(func):  
        def wrapper(*args, **kwargs):  
            attempts = 0  
            while attempts < max_attempts:  
                try:  
                    return func(*args, **kwargs)  
                except Exception as e:  
                    attempts += 1  
                    print(f"Attempt {attempts} failed: {e}")  
            raise Exception("Max retries reached!")  
        return wrapper  
    return decorator  

@retry(max_attempts=2)  
@validate_input  
def calculate_square_root(x):  
    return x ** 0.5  

3. Caching + Rate Limiting

Store results and prevent excessive calls.

from functools import lru_cache  

def rate_limit(max_calls=5):  
    def decorator(func):  
        call_count = 0  
        def wrapper(*args, **kwargs):  
            nonlocal call_count  
            if call_count >= max_calls:  
                raise Exception("Rate limit exceeded!")  
            call_count += 1  
            return func(*args, **kwargs)  
        return wrapper  
    return decorator  

@rate_limit(max_calls=3)  
@lru_cache  
def fetch_data(url):  
    # API call here  
    pass  

Watch Out for Pitfalls!

While stacking decorators is powerful, be mindful of:

🔹 Order dependency@A @B is not the same as @B @A.
🔹 Debugging complexity – Too many layers can make stack traces harder to read.
🔹 Performance overhead – Each decorator adds a tiny delay.


Your Turn: Experiment & Share!

The best way to master decorator stacking? Try it yourself!

  • What happens if you stack @lru_cache with @timer?
  • Can you chain four or more decorators?
  • What creative combinations can you come up with?

Drop your experiments in the comments—we’d love to see what you build! 🚀

PythonTricks #CodeSmarter

Decorators vs. Regular Functions: What’s the Difference?