Learning Objectives
After completing this lesson, you will be able to:- Apply decorators to generator functions to monitor, time, or log value generation.
- Understand the execution flow differences when decorating normal functions vs. generator functions.
- Combine multiple generators into data pipelines and manage them using decorators.
- Use closures to configure custom iterators dynamically.
Pattern 1: Decorating Generators
Decorators are commonly used to log, time, or rate-limit functions. However, when you apply a decorator to a generator function, you must be careful about when code executes.The Generator Decorator Nuance
Recall that calling a generator function does not execute its body; it immediately returns a generator object. If a standard decorator wraps a generator, the wrapper function will run when the generator is created, but not when values are yielded. To intercept the actual value generation, the decorator’s wrapper must iterate through the generator and yield the values.Example: Yield Logger
Here is a decorator that logs every time a generator yields a value:Practice
Exercise 1
Predict the output of the following code:Solution
Solution
my_generator() immediately triggers the wrapper print_call which prints "Decorator Wrapper Called" and returns the generator object. The code inside my_generator() (printing "Generator Body Started") runs only when next(g) is called.Pattern 2: Stateful Iterators with Closures
Closures can be used to dynamically configure custom iterators without defining a full class structure. By combining a closure with a generator, we can maintain configuration and iteration state cleanly.Example: Configurable Fibonacci Generator
Practice
Exercise 1
What is the main benefit of using a closure to create configured generators?Solution
Solution
It allows you to enclose configuration values (like
limit or multiplier) in the outer scope, keeping the generator function signature clean and modular.Pattern 3: Decorated Data Pipelines
In real-world data science and machine learning applications, generators are often chained together to process large streams of data. Decorators can be used to monitor metrics (like execution time, memory usage, or record count) across the entire pipeline.Example: Monitoring a Pipeline
Check Your Understanding
Question 1 Why does a standard decorator that simply executes a wrapped function not work out-of-the-box for logging yields inside a generator?Solution
Solution
Because a generator function returns a generator object immediately when called instead of running the code block. To log yields, the decorator’s wrapper function must actively iterate over the generator object and yield each item.
Solution
Solution
True. Each outer function call creates a new scope (closure), ensuring that configuration states and variables do not leak or interfere with other generator instances.
Solution
Solution
They allow you to add auxiliary tasks (like performance profiling, error handling, rate limiting, and item auditing) to each pipeline step cleanly without polluting the core data-transformation logic.