Courses/Python/Decorators & Advanced Features

    Lesson 14 • Expert

    Decorators & Advanced Features

    Decorators are one of Python's most powerful features — used in Django, Flask, FastAPI, TensorFlow, PyTorch, logging systems, authentication, caching, and more. This lesson takes you far beyond the basics.

    What You'll Learn in This Lesson

    • • What a decorator is and how Python applies @ syntax
    • • Writing your own decorators from scratch with functools.wraps
    • • Stacking multiple decorators and decorator factories with arguments
    • • Practical use cases: timing, logging, access control, caching
    • • How real frameworks (Django, FastAPI) use advanced decorator patterns

    What You'll Learn

    ✔ How decorators truly work under the hood

    ✔ How to pass arguments to decorators

    ✔ How to stack multiple decorators safely

    ✔ How to preserve metadata (the functools.wraps problem)

    ✔ How real frameworks use advanced decorator patterns

    ✔ How to build your own production-ready decorator utilities

    🔥 1. Decorators Refresher (1-Minute Summary)

    🏠 Real-World Analogy:

    Think of a decorator like gift wrapping. You have a present (your function), and the wrapper adds something extra (like a bow or ribbon) without changing what's inside. The wrapper can add behavior before or after opening the gift!

    StepWhat Happens
    1. Define decoratorCreate a function that takes another function as input
    2. Create wrapperInside, define a wrapper function that adds behavior
    3. Return wrapperReturn the wrapper (not call it!)
    4. Apply with @Use @decorator_name above a function

    The minimal example:

    Basic Decorator

    The simplest decorator pattern

    Try it Yourself »
    Python
    # Step 1: Define the decorator function
    def my_decorator(fn):      # Takes a function as input
        # Step 2: Create an inner "wrapper" function
        def wrapper():
            print("Before")    # Runs BEFORE the original function
            fn()               # Call the original function
            print("After")     # Runs AFTER the original function
        # Step 3: Return the wrapper (don't call it!)
        return wrapper
    
    # Step 4: Apply decorator using @ syntax
    @my_decorator
    def hello():
        print("Hello!"
    ...

    Try It Yourself: Decorators

    Practice creating and using decorators in Python

    Try it Yourself »
    Python
    # Decorators Practice
    
    import time
    from functools import wraps
    
    # Basic decorator
    def my_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("Before the function")
            result = func(*args, **kwargs)
            print("After the function")
            return result
        return wrapper
    
    @my_decorator
    def say_hello(name):
        print(f"Hello, {name}!")
    
    say_hello("Alice")
    
    print("\n" + "="*30 + "\n")
    
    # Timer decorator
    def timer(func):
        @wraps(func)
        def wrapper(*args, 
    ...

    🧠 2. Why Decorators Matter in Real Projects

    Every major Python ecosystem uses decorators for:

    Authentication Decorator

    Protect routes with login check

    Try it Yourself »
    Python
    # Authentication decorator example
    def require_login(fn):
        def wrapper(user, *args, **kwargs):
            if not user.get("logged_in"):
                return "Please log in first!"
            return fn(user, *args, **kwargs)
        return wrapper
    
    @require_login
    def get_dashboard(user):
        return f"Welcome to your dashboard, {user['name']}!"
    
    # Test with logged in user
    logged_in_user = {"name": "Alice", "logged_in": True}
    print(get_dashboard(logged_in_user))
    
    # Test with logged out user
    logged_out_user 
    ...

    ✔ Logging & monitoring

    ✔ Rate limiting

    ✔ Permissions

    ✔ Dependency injection (FastAPI)

    ✔ ML model preprocessing

    Understanding decorators = understanding real frameworks.

    ⚙️ 3. The Core Issue — Decorators Remove Metadata

    ⚠️ The Problem:

    When you wrap a function, Python "forgets" the original function's name, docstring, and other info. This breaks documentation tools, debugging, and frameworks like FastAPI!

    Without @wrapsWith @wraps ✅
    func.__name__ → "wrapper"func.__name__ → original name
    func.__doc__ → Nonefunc.__doc__ → original docstring
    Debugger shows "wrapper"Debugger shows real function name

    functools.wraps

    Preserve function metadata

    Try it Yourself »
    Python
    from functools import wraps
    
    # ❌ BAD: Without wraps - loses metadata
    def bad_decorator(fn):
        def wrapper(*a, **k):
            return fn(*a, **k)
        return wrapper  # Returns wrapper, not fn!
    
    @bad_decorator
    def greet():
        """Says hello"""
        return "Hello!"
    
    print("Without @wraps:")
    print(f"  Name: {greet.__name__}")  # 'wrapper' - WRONG!
    print(f"  Doc: {greet.__doc__}")    # None - WRONG!
    
    # ✅ GOOD: With wraps - preserves metadata
    def good_decorator(fn):
        @wraps(fn)  # ← This line fixes 
    ...

    🎯 4. Decorators That Accept Arguments (Decorator Factories)

    🤔 The Challenge:

    What if you want @check_role("admin")? You need to pass an argument to the decorator! This requires an extra layer of nesting called a decorator factory.

    LayerPurposeReturns
    Outer functionAccepts decorator argumentsReturns the actual decorator
    Middle functionThe actual decorator (takes fn)Returns the wrapper
    Inner functionThe wrapper that runsCalls the original fn

    Decorator Factory

    Decorators with arguments

    Try it Yourself »
    Python
    from functools import wraps
    
    # Layer 1: Outer function accepts the argument
    def check_role(required_role):
        # Layer 2: This is the actual decorator
        def decorator(fn):
            @wraps(fn)
            # Layer 3: This is the wrapper that runs
            def wrapper(user, *args, **kwargs):
                if user.get("role") != required_role:
                    raise PermissionError(f"Access denied. Required: {required_role}")
                return fn(user, *args, **kwargs)
            return wrapper
        return de
    ...

    🔄 5. Stacking Multiple Decorators (Order Matters!)

    🏠 Analogy: Layers of Wrapping Paper

    Imagine wrapping a gift with multiple layers. The closest decorator to the function wraps first, then the next one wraps around that, and so on. When you call the function, you "unwrap" from outside in.

    Code OrderExecution Order
    @A (top)A runs FIRST (outermost layer)
    @B (bottom)B runs SECOND (inner layer)
    def func:Function runs LAST (the core)

    Stacking Decorators

    Order matters in decorator chains

    Try it Yourself »
    Python
    from functools import wraps
    
    def decorator_A(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            print("A: before")   # Runs 1st
            result = fn(*a, **k) # Calls B's wrapper
            print("A: after")    # Runs 6th
            return result
        return wrapper
    
    def decorator_B(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            print("B: before")   # Runs 2nd
            result = fn(*a, **k) # Calls the real function
            print("B: after")    # Runs 4th
            return result
        return wrapper
    
    ...

    ⚡ 6. Example: Timing + Logging + Caching Stack

    Full Decorator Stack

    Timer, logger, and cache combined

    Try it Yourself »
    Python
    import time
    from functools import wraps
    
    def timer(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            start = time.time()
            result = fn(*a, **k)
            print(f"⏱ {fn.__name__} took {time.time() - start:.4f}s")
            return result
        return wrapper
    
    def logger(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            print(f"📘 Calling {fn.__name__}")
            return fn(*a, **k)
        return wrapper
    
    def cache(fn):
        saved = {}
        @wraps(fn)
        def wrapper(x):
            if x in saved:
       
    ...

    🧩 7. Real Project Example — Retry Decorator

    Used in API calls, database queries, and cloud services.

    Retry Decorator

    Auto-retry failed operations

    Try it Yourself »
    Python
    from functools import wraps
    import time
    import random
    
    def retry(times):
        def decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                for i in range(times):
                    try:
                        return fn(*args, **kwargs)
                    except Exception as e:
                        print(f"Retry {i+1}/{times}: {e}")
                        time.sleep(0.1)
                raise RuntimeError("All retries failed")
            return wrapper
        return decorator
    
    @retry(3)
    def un
    ...

    🔒 8. Real Project Example — Input Validation Decorator

    Input Validation

    Type-check function arguments

    Try it Yourself »
    Python
    from functools import wraps
    
    def validate_types(*types):
        def decorator(fn):
            @wraps(fn)
            def wrapper(*args):
                for arg, expected in zip(args, types):
                    if not isinstance(arg, expected):
                        raise TypeError(f"Expected {expected.__name__}, got {type(arg).__name__}")
                return fn(*args)
            return wrapper
        return decorator
    
    @validate_types(int, int)
    def add(a, b):
        return a + b
    
    print(add(5, 3))
    
    try:
        print(add("5", 3)
    ...

    🧠 9. Passing Multiple Arguments to Decorators

    Multiple Arguments

    Decorators with multiple parameters

    Try it Yourself »
    Python
    from functools import wraps
    
    def require_roles(*allowed):
        def decorator(fn):
            @wraps(fn)
            def wrapper(user, *a, **k):
                if user.get("role") not in allowed:
                    return f"Access denied. Allowed roles: {allowed}"
                return fn(user, *a, **k)
            return wrapper
        return decorator
    
    @require_roles("admin", "moderator")
    def edit_post(user, post_id):
        return f"{user['name']} edited post {post_id}"
    
    admin = {"name": "Alice", "role": "admin"}
    mod = 
    ...

    🧵 10. Decorators With Keyword Arguments

    Keyword Arguments

    Configurable decorators

    Try it Yourself »
    Python
    from functools import wraps
    
    def log(prefix="INFO", suffix=""):
        def decorator(fn):
            @wraps(fn)
            def wrapper(*a, **k):
                print(f"[{prefix}] Calling {fn.__name__} {suffix}")
                return fn(*a, **k)
            return wrapper
        return decorator
    
    @log(prefix="DEBUG", suffix="- testing")
    def load_config():
        return {"debug": True}
    
    @log(prefix="WARN")
    def risky_operation():
        return "done"
    
    load_config()
    risky_operation()

    🎓 11. Advanced Pattern — Class-Based Decorators

    🤔 When to Use Classes?

    Use class-based decorators when you need to track state across multiple calls (like counting calls, caching results, or enforcing rate limits).

    Function DecoratorClass Decorator
    Simple, one-off behaviorNeeds state between calls
    Uses closures for stateUses self attributes
    3 nested functions maxCleaner for complex logic

    Class-Based Decorator

    Stateful decorators with classes

    Try it Yourself »
    Python
    from functools import wraps
    
    class RateLimiter:
        def __init__(self, limit):
            self.limit = limit   # Max allowed calls
            self.calls = 0       # State: track call count
        
        def __call__(self, fn):  # Makes the class callable as decorator
            @wraps(fn)
            def wrapper(*a, **k):
                if self.calls >= self.limit:
                    return f"Rate limit exceeded ({self.limit} calls max)"
                self.calls += 1  # Update state
                return fn(*a, **k)
           
    ...

    🧨 12. Debugging Decorators (Common Problems)

    These are the most common mistakes when writing decorators:

    ❌ MistakeWhat Happens✅ Fix
    Forgetting @wrapsFunction name/docs disappearAlways add @wraps(fn)
    Wrong decorator orderUnexpected behaviorRemember: bottom wraps first
    Missing *args, **kwargsArguments don't pass throughAlways use wrapper(*a, **k)
    Forgetting to return resultFunction returns NoneAdd return fn(*a, **k)
    Calling instead of returning wrapperDecorator runs immediatelyUse return wrapper not return wrapper()

    ❌ Forgetting wraps

    → Breaks documentation, tooling, introspection, FastAPI routes, pytest

    ❌ Using wrong order in stacks

    → Decorators run in unexpected order; auth might run after logging

    ❌ Forgetting closure rules

    → Modifying outer variable without nonlocal causes errors

    🧪 13. Mini Project — Build a Full Decorator Suite

    Build decorators for timing, logging, validation, caching, retries, and permissions:

    Full Decorator Suite

    Framework-level decorator system

    Try it Yourself »
    Python
    from functools import wraps
    import time
    
    def logger(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            print(f"📘 {fn.__name__} called")
            return fn(*a, **k)
        return wrapper
    
    def timer(fn):
        @wraps(fn)
        def wrapper(*a, **k):
            start = time.time()
            result = fn(*a, **k)
            print(f"⏱ {time.time() - start:.4f}s")
            return result
        return wrapper
    
    def retry(times):
        def decorator(fn):
            @wraps(fn)
            def wrapper(*a, **k):
                for i in 
    ...

    You now have a framework-level decorator system.

    🎉 Conclusion

    You now understand:

    ✔ How decorators truly work

    ✔ How to create decorators that accept arguments

    ✔ How to stack and combine decorators

    ✔ How to preserve metadata

    ✔ How closures power all decorators

    ✔ How major Python frameworks implement them

    ✔ How to design your own production-ready decorator system

    📋 Quick Reference — Decorators

    SyntaxWhat it does
    @my_decoratorApply decorator to a function
    functools.wraps(fn)Preserve original function metadata
    @staticmethodMethod that needs no self
    @classmethodMethod that receives the class
    @lru_cacheCache results for repeated calls

    🏆 Lesson Complete!

    You can now write, stack, and configure decorators for any use case — the same pattern used by Flask, Django, and FastAPI.

    Up next: Advanced Functions — master *args, **kwargs, and function signature tools.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service