Learning Objectives: After this lesson, you'll understand closures deeply, build function and class decorators from scratch, use functools effectively, and apply real-world decorator patterns in production code.
How Decorators Transform Functions
Before diving into code, let's visualize what decorators actually do. A decorator wraps a function, adding behavior before and/or after the original function runs.
When you write @timer above a function, Python replaces that function with the wrapped version. The original function still exists—it's just called from inside the wrapper.
The Foundation: Understanding Closures
Before diving into decorators, we need to solidify our understanding of closures—the mechanism that makes decorators work.
What is a Closure?
A closure is a function that remembers values from its enclosing scope even after that scope has finished executing. Let's visualize how scope works:
Visualizing Closure Memory
Closures capture variables by reference, not by value. This visualization shows how multiple closures can share the same memory cell:
Function Decorators: The Basics
A decorator is simply a function that takes a function as input and returns a new function. Let's trace through the execution:
The Call Stack During Decoration
The @ Syntax
The @decorator syntax is just syntactic sugar. Here's how stacked decorators work:
Preserving Function Metadata with functools
Decorators with Arguments
Creating decorators that accept arguments requires an extra level of nesting. Let's visualize this three-layer structure:
Class-Based Decorators
Decorators can also be implemented as classes using __call__.
Decorating Classes
Decorators can also modify classes, not just functions.
Real-World Decorator Patterns
Authentication and Authorization
Caching and Memoization
Input Validation
Advanced: Decorators That Work on Methods and Functions
Practice Exercises
Exercise 1: Rate Limiter Decorator
Exercise 2: Deprecation Warning Decorator
Key Takeaways
| Concept | Description |
|---|---|
| Closures | Functions that capture and remember variables from enclosing scope |
@wraps | Preserves original function's metadata when decorating |
| Decorator arguments | Require factory function that returns the actual decorator |
| Class decorators | Use __call__ to make instances callable |
| Method decorators | Need careful handling of self parameter |
lru_cache | Built-in memoization with LRU eviction |
Common Decorator Patterns
- Logging/Tracing - Track function calls and results
- Timing - Measure execution time
- Caching - Store results for repeated calls
- Retry - Automatically retry on failure
- Validation - Check inputs before execution
- Authentication - Verify access permissions
- Rate limiting - Control call frequency
Next Steps
In the next lesson, we'll explore Generators and Iterators—learn the iterator protocol, build generators with yield, understand lazy evaluation, and see how generators relate to coroutines and async programming.
Ready to master lazy evaluation? Generators await in the next lesson!