🎯 What You'll Master
Decorators are one of Python's most elegant features, allowing you to modify or enhance functions and classes without changing their source code. In this part, you'll learn to write clean, maintainable, and powerful code using decorators for everything from logging and caching to authentication and validation. By the end, you'll be crafting sophisticated decorator patterns used by popular frameworks like Flask and FastAPI.
Function Fundamentals for Decorators
5.1 First-Class Functions
- Functions as objects - understanding the fundamental concept
- Assigning functions to variables and storing them in data structures
- Passing functions as arguments to other functions
- Returning functions from functions - the foundation of decorators
5.2 Closures and Scope
- Understanding the
nonlocalkeyword and when to use it - How closures capture and remember variables from outer scope
- Practical closure examples for data encapsulation
- The difference between closure and global scope
Key Concepts to Master
First-Class Functions Closures Scope (LEGB) nonlocal Keyword Function References🏋️ Lab Exercise: Simple Memoization Function
Task: Implement a simple memoization function using closures that caches function results to avoid redundant calculations.
Requirements:
- Create a
memoizefunction that takes another function as input - Use a closure to maintain a cache dictionary
- Cache function results based on arguments
- Return cached results when the same arguments are provided
- Demonstrate proper closure usage and scope management
Hint: Your solution structure should look like:
- Outer function:
memoize(func) - Create an empty cache dictionary in the outer scope
- Inner function:
wrapper(*args) - Check if args are in cache; if yes, return cached value
- If not in cache, call the function, cache the result, and return it
- Test with an expensive function like fibonacci
Decorator Basics
6.1 What Are Decorators?
- The decorator design pattern vs Python's decorator syntax
- Understanding the
@decorator_namesyntactic sugar - How decorators wrap functions to modify behavior
- The relationship between decorators, closures, and higher-order functions
6.2 Building Your First Decorator
- Simple logging decorator - tracking function calls
- Timer/performance decorator - measuring execution time
- Common pitfalls: losing function metadata
- Using
functools.wrapsto preserve function information
6.3 Decorators with Arguments
- Creating configurable decorators with parameters
- Understanding the decorator factory pattern (three levels of nesting)
- Practical example:
@repeat(times=3) - When and why to use parameterized decorators
Key Concepts to Master
@ Syntax Wrapper Functions functools.wraps Decorator Factories *args, **kwargs🏋️ Lab Exercise: Debug Decorator
Task: Build a @debug decorator that prints function calls with their arguments and return values.
Requirements:
- Create a decorator that logs function name, arguments, and return value
- Handle both positional and keyword arguments properly
- Use
functools.wrapsto preserve function metadata - Format the output to be readable (e.g., "Calling function_name(arg1, arg2, kwarg1=value)")
- Print the return value after function execution
Hint: Your solution structure should look like:
- Import
functools - Define
debug(func)decorator - Use
@functools.wraps(func)on the wrapper - In wrapper, format args and kwargs as strings
- Print "Calling {func.__name__}(...)"
- Call the function and capture result
- Print "Returned: {result}"
- Return the result
Advanced Decorator Patterns
7.1 Class-Based Decorators
- Using the
__call__method to create callable classes - When to use class decorators vs function decorators
- Maintaining state across multiple function calls
- Advantages: readability and state management for complex decorators
7.2 Method Decorators
- Decorating class methods and handling the
selfparameter - Deep dive into
@staticmethod,@classmethod,@property - Creating custom method decorators for validation and authorization
- Differences between decorating functions and methods
7.3 Class Decorators
- Decorating entire classes to modify their behavior
- Adding methods or attributes to classes automatically
- Implementing the Singleton pattern using class decorators
- Use cases: registration, monitoring, and automatic method generation
7.4 Stacking Multiple Decorators
- Understanding the order of execution when stacking decorators
- Bottom-to-top evaluation: how Python processes stacked decorators
- Designing decorators that work well together (composability)
- Common patterns and potential pitfalls
Key Concepts to Master
__call__ Method Class Decorators Method Decorators Decorator Stacking Singleton Pattern🏋️ Lab Exercise: Argument Validation Decorator
Task: Create a @validate decorator for function argument validation that raises appropriate errors when arguments don't meet requirements.
Requirements:
- Implement both function-based and class-based versions
- Accept validation rules: type checks, range checks, custom validators
- Raise
ValueErrororTypeErrorwith descriptive messages - Support validating both positional and keyword arguments
- Example usage:
@validate(x=int, y=(int, lambda y: y > 0))
Hint: Your solution structure should look like:
- Function-based: Create a decorator factory that accepts validation rules as kwargs
- Return a decorator that returns a wrapper function
- In wrapper, inspect function signature and match args to rules
- Check types and run custom validators
- Class-based: Use
__init__to store rules,__call__to wrap function - Store the wrapped function and rules as instance attributes
Real-World Decorator Applications
8.1 Common Use Cases
- Authentication and authorization - protecting sensitive operations
- Caching and memoization - optimizing expensive computations
- Rate limiting and throttling - preventing abuse and overload
- Retry logic with exponential backoff - handling transient failures
- Deprecation warnings - gracefully phasing out old code
8.2 Decorators in Popular Frameworks
- Flask/FastAPI route decorators - mapping URLs to functions
- Property decorators for getters/setters - clean attribute access
- Testing decorators:
@patch,@mockfrom unittest - Django decorators:
@login_required,@permission_required - How frameworks leverage decorators for clean, declarative APIs
Key Concepts to Master
Authentication Caching Rate Limiting Retry Logic Route Registration Property Pattern🏋️ Lab Exercise: API Endpoint Decorator System
Task: Build an API endpoint decorator system that combines multiple concerns in a modular, composable way.
Requirements:
- Create an
@auth_requireddecorator that checks authentication - Create a
@rate_limit(max_calls=10, window=60)decorator - Create a
@log_requestdecorator that logs function calls - Create an
@handle_errorsdecorator for automatic error handling - Demonstrate stacking these decorators on API endpoint functions
- Make sure decorators work well together and maintain proper order
Hint: Your solution structure should look like:
auth_required: Check for auth token/header, raise exception if missingrate_limit: Use a dictionary to track calls per user/IP with timestampslog_request: Print timestamp, function name, and argumentshandle_errors: Wrap in try/except, return error JSON on exception- Test by stacking decorators: @handle_errors @rate_limit(10, 60) @auth_required @log_request
🎓 Part 2 Capstone Project: Web Framework Mini
Build a lightweight web framework that demonstrates mastery of decorator patterns!
Project Requirements:
- Route Registration System
- Use decorators to map URLs to handler functions
- Support multiple HTTP methods (GET, POST, PUT, DELETE)
- Example:
@app.route("/home"),@app.route("/api/users", methods=["POST"]) - Store route mappings in a dictionary
- Middleware System
- Authentication decorator:
@require_auth - Logging decorator:
@log_requests - Timing decorator:
@measure_time - Middleware should be composable and stackable
- Authentication decorator:
- Request/Response Validation
- Create decorators that validate incoming request data
- Format responses automatically (JSON, HTML, etc.)
- Example:
@validate_json(schema),@returns_json
- Caching Decorator
- Implement
@cache(ttl=300)for expensive operations - Support configurable time-to-live (TTL)
- Cache key should be based on function args
- Automatic cache invalidation
- Implement
- Error Handling & Rate Limiting
- Decorators that catch exceptions and return HTTP error responses
- Per-route rate limiting:
@rate_limit(calls=100, period=60) - Track requests per user/IP address
Example Usage:
app = WebFramework()@app.route("/api/users")
@require_auth
@rate_limit(calls=10, period=60)
@cache(ttl=300)
@returns_json
def get_users():
return User.get_all()@app.route("/api/users", methods=["POST"])
@require_auth
@validate_json(user_schema)
@returns_json
def create_user(data):
return User.create(data)
Evaluation Criteria:
- ✅ Use both function-based and class-based decorators appropriately
- ✅ Implement decorator factories for configurable behavior
- ✅ Demonstrate proper decorator stacking and composition
- ✅ Include comprehensive error handling and validation
- ✅ Write unit tests for all decorator functionality
- ✅ Document code with clear docstrings and type hints
Bonus Challenges:
- 🌟 Add support for URL parameters and wildcards
- 🌟 Implement decorator-based permission system with role-based access control
- 🌟 Create profiling decorator that tracks performance metrics
- 🌟 Build decorator that auto-generates API documentation from signatures
📚 Part 2 Summary
What You've Learned:
- Module 5: First-class functions, closures, and scope fundamentals
- Module 6: Decorator basics, syntax, and creating parameterized decorators
- Module 7: Advanced patterns including class-based decorators and decorator stacking
- Module 8: Real-world applications in authentication, caching, and framework design
Key Takeaways:
- Decorators are syntactic sugar for function wrappers that modify behavior
- Closures allow decorators to maintain state and access outer scope
- functools.wraps is essential for preserving function metadata
- Decorator factories enable parameterized decorators with configuration
- Stacking decorators requires understanding execution order (bottom-to-top)
- Class-based decorators use __call__ and are great for stateful decorators
Next Steps:
Ready to continue? Move on to:
- Part 3: Asynchronous Programming - Master async/await, concurrent task execution, and building high-performance async applications

