Courses/Python/Custom Decorators

    Advanced Lesson

    Building & Reusing Custom Decorator Libraries

    Design reusable decorator libraries that power frameworks, APIs, and production systems

    Why Build Your Own Decorator Library?

    High-level Python systems need common behaviors that decorators provide:

    • Logging — Track function calls and results
    • Caching — Store expensive computation results
    • Error handling & retries — Recover from failures
    • Validation — Enforce input/output constraints
    • Timing — Measure performance
    • Access control — Enforce permissions
    • Registration — Build plugin systems

    A centralized decorator library provides tested utilities, ensures consistent behavior, and speeds up development.

    The Core Pattern

    Every reusable decorator follows this pattern. Always use functools.wraps to preserve metadata.

    Decorator Template

    Try it Yourself »
    Python
    import functools
    
    def decorator_template(func):
        """Template for creating decorators."""
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Before: code that runs before the function
            print(f"Calling {func.__name__}")
            
            # Execute the original function
            result = func(*args, **kwargs)
            
            # After: code that runs after the function
            print(f"Finished {func.__name__}")
            
            return result
        
        return wrapper
    
    
    ...

    Reusable Timer Decorator

    A practical decorator for measuring function execution time.

    Timing Decorator

    Try it Yourself »
    Python
    import functools
    import time
    
    def timing(func):
        """Measure and print function execution time."""
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            duration = time.perf_counter() - start
            print(f"{func.__name__} took {duration:.4f}s")
            return result
        
        return wrapper
    
    # Example usage
    @timing
    def calculate_sum(n):
        """Calculate sum of numbers from 1 to n."""
        return sum(
    ...

    Decorators With Arguments (Decorator Factories)

    To accept arguments, wrap your decorator in another function that returns the actual decorator.

    Retry Decorator Factory

    Try it Yourself »
    Python
    import functools
    import time
    
    def retry(times=3, delay=0):
        """
        Retry a function if it fails.
        
        Args:
            times: Number of retry attempts
            delay: Seconds to wait between retries
        """
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                last_exception = None
                
                for attempt in range(1, times + 1):
                    try:
                        return func(*args, **kwargs)
                    except Excepti
    ...

    Reusable Logging Decorator

    Log function calls with customizable logger for debugging and monitoring.

    Logging Decorator

    Try it Yourself »
    Python
    import functools
    
    def log_calls(logger=print):
        """
        Log function calls with arguments and return values.
        
        Args:
            logger: Callable for logging (default: print)
        """
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                # Format arguments
                args_str = ", ".join(repr(a) for a in args)
                kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
                all_args = ", ".join(filter(None, [args_st
    ...

    Type Validation Decorator

    Enforce type checking using function annotations for safer code.

    Type Validation Decorator

    Try it Yourself »
    Python
    import functools
    import inspect
    
    def enforce_types(func):
        """Validate argument types based on annotations."""
        
        sig = inspect.signature(func)
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Bind arguments to parameters
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            
            # Check each parameter
            for param_name, param_value in bound.arguments.items():
                param = sig.parameters[param_name]
                
         
    ...

    Access Control Decorator

    Enforce role-based access control, commonly used in web frameworks and APIs.

    Access Control Decorator

    Try it Yourself »
    Python
    import functools
    
    def require_role(required_role):
        """
        Require a specific role to execute function.
        
        Args:
            required_role: Role required to access the function
        """
        def decorator(func):
            @functools.wraps(func)
            def wrapper(user, *args, **kwargs):
                if not hasattr(user, 'role'):
                    raise AttributeError("User object must have 'role' attribute")
                
                if user.role != required_role:
                    raise Permission
    ...

    Memoization / Caching Decorator

    Cache expensive function results to improve performance.

    Memoization Decorator

    Try it Yourself »
    Python
    import functools
    
    def memoize(func):
        """Cache function results based on arguments."""
        cache = {}
        
        @functools.wraps(func)
        def wrapper(*args):
            if args not in cache:
                print(f"Computing {func.__name__}{args}...")
                cache[args] = func(*args)
            else:
                print(f"Using cached result for {func.__name__}{args}")
            
            return cache[args]
        
        # Expose cache for inspection
        wrapper.cache = cache
        return wrapper
    
    # Example: 
    ...

    Registry Pattern for Plugins

    Automatically register functions or classes for plugin systems and routing.

    Registry Pattern

    Try it Yourself »
    Python
    import functools
    
    # Global registry
    command_registry = {}
    
    def register_command(name):
        """
        Register a function as a command handler.
        
        Args:
            name: Command name for the registry
        """
        def decorator(func):
            command_registry[name] = func
            
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)
            
            return wrapper
        return decorator
    
    # Register handlers
    @register_command("greet")
    def handl
    ...

    Async-Compatible Decorators

    Support both sync and async functions in your decorator library.

    Universal Timer

    Try it Yourself »
    Python
    import functools
    import inspect
    import time
    
    def universal_timer(func):
        """Timer that works with both sync and async functions."""
        
        if inspect.iscoroutinefunction(func):
            # Async version
            @functools.wraps(func)
            async def async_wrapper(*args, **kwargs):
                start = time.perf_counter()
                result = await func(*args, **kwargs)
                duration = time.perf_counter() - start
                print(f"{func.__name__} took {duration:.4f}s (async)")
          
    ...

    Stacking Decorators Properly

    Multiple decorators execute bottom-to-top, but wrap top-to-bottom. Order matters!

    Decorator Stacking

    Try it Yourself »
    Python
    import functools
    
    # Three simple decorators
    def decorator_a(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("A: Before")
            result = func(*args, **kwargs)
            print("A: After")
            return result
        return wrapper
    
    def decorator_b(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("B: Before")
            result = func(*args, **kwargs)
            print("B: After")
            return result
        return wrapper
    
    def decorator_c(func
    ...

    Building a Complete Decorator Library

    Organize decorators into a reusable library structure for production use.

    Decorator Library Structure

    Try it Yourself »
    Python
    """
    Example decorator library structure:
    
    decorlib/
        __init__.py          # Public API exports
        timing.py            # Performance decorators
        logging.py           # Logging decorators
        validation.py        # Input validation
        caching.py           # Memoization
        retry.py             # Error handling
        auth.py              # Access control
        registry.py          # Plugin registration
        async_tools.py       # Async utilities
        
    Usage:
        from decorlib import timing, log_
    ...

    Advanced: Exponential Backoff Retry

    Production-grade retry decorator with exponential backoff and jitter.

    Exponential Backoff Retry

    Try it Yourself »
    Python
    import functools
    import time
    import random
    
    def backoff_retry(max_attempts=3, base_delay=0.1, max_delay=10, jitter=True):
        """
        Retry with exponential backoff.
        
        Args:
            max_attempts: Maximum number of retry attempts
            base_delay: Initial delay in seconds
            max_delay: Maximum delay between retries
            jitter: Add random jitter to prevent thundering herd
        """
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
           
    ...

    Summary

    You've learned to build production-grade decorator libraries:

    • Core decorator pattern with functools.wraps
    • Decorator factories for parameterized decorators
    • Timing and performance measurement
    • Logging with customizable loggers
    • Type validation using annotations
    • Access control and permissions
    • Memoization and caching
    • Registry patterns for plugins
    • Async-compatible decorators
    • Proper decorator stacking
    • Exponential backoff retry
    • Library organization and best practices

    Decorator libraries power major frameworks like Django, FastAPI, Flask, and Celery. A well-designed decorator library becomes reusable infrastructure that speeds up development across multiple projects.

    📋 Quick Reference — Decorator Libraries

    PatternUse case
    @functools.wraps(fn)Preserve original __name__ and __doc__
    def decorator(fn): ...Basic decorator factory
    def with_args(n): ...Decorator that accepts arguments
    class-based decoratorDecorator with persistent state
    @retry(max=3, delay=1)Parameterised production decorator

    🎉 Great work! You've completed this lesson.

    You can now build reusable decorator libraries — retry logic, rate limiting, caching, auth guards — used across entire codebases.

    Up next: SQLite & ORMs — work with databases using both raw SQL and SQLAlchemy.

    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