Python › Comprehensions & Generators

How does lazy evaluation in a chained sequence of generator expressions actually process elements, one at a time end-to-end, without intermediate lists?

Advanced

Answer

Each generator expression only pulls from its SOURCE generator when IT is asked for its own next value, propagating the request all the way back to the original iterable — meaning a chain of generator expressions processes ONE element completely through the entire pipeline before moving to the next, rather than processing each stage across the whole dataset before the next stage begins.
data = range(1_000_000)
pipeline = (x * 2 for x in (y + 1 for y in data if y % 2 == 0))
first_five = [next(pipeline) for _ in range(5)]
# Only the first ~10 elements of 'data' were ever actually touched, not the whole million
Real-world example Processing a huge dataset through a multi-stage transformation pipeline while only ever holding one element's worth of state at each stage.

Common follow-ups: How does this pull-based, element-at-a-time evaluation order differ from how a similarly-chained list comprehension would behave?