Decorators and Generators represent two of Python's most elegant paradigms for advanced control flow. Decorators provide meta-programming capabilities to extend function behaviour without modifying source code, while Generators offer lazy evaluation to stream dataset sequences with minimal memory overhead.
1. Higher-Order Functions & Closures
In Python, functions are first-class objects. They can be assigned to variables, passed as arguments, and returned from other functions. A closure occurs when an inner function retains access to variables defined in its enclosing scope, even after the outer function finishes execution.
2. Python Decorators Syntax & Design Patterns
A decorator takes a target function as input, wraps it with additional behavior (logging, authentication, timing), and returns the wrapper callable. The @decorator syntax is syntactic sugar for func = decorator(func).
Parameterized Decorators
Passing arguments to decorators requires adding an outer factory level to accept arguments and return the actual decorator.
3. Generators and the yield Statement
Unlike standard functions that execute and return a single final value via return, a Generator function contains one or more yield statements. When called, it returns a generator iterator that yields execution values sequentially on demand.
4. Decorators vs. Generators Comparison
| Concept | Primary Purpose | Key Syntax / Keywords | Core Advantage |
|---|---|---|---|
| Decorator | Modify, wrap, or extend the behavior of existing callables. | @decorator_name, @wraps |
DRY principle, clear separation of concerns (logging, auth, caching). |
| Generator | Stream calculated output values lazily over time. | yield, yield from |
Memory efficiency $O(1)$, process unbounded or massive datasets safely. |
🏋️ Try It Yourself: Practice Challenges
Build a custom decorator `@memoize` that caches function results in an internal dictionary to speed up expensive recursive calls like computing Fibonacci numbers.
Write a generator function `infinite_primes()` that indefinitely yields prime numbers on demand without running out of bounds.
Implement a parameterized decorator `@require_role("admin")` that checks user authorization before permitting wrapper execution.
Create two chaining generator functions: one to generate random integer log entries, and another to filter and format warning logs on the fly.
Design a decorator `@rate_limit(max_calls=3, period=5)` that limits execution frequency to a maximum count within a time window.
Write nested generator functions that utilize `yield from` syntax to flatten multiple nested sequences into a single continuous sequence.