1. Introduction
A generator is a special kind of function that produces a sequence of values lazily, one at a time, using the 'yield' keyword instead of 'return'. Unlike a regular function that computes and returns everything at once, a generator pauses at each yield and resumes exactly where it left off on the next call.
Cricket analogy: A commentator doesn't dump the whole match summary at once; like a bowler running in over after over, a generator yields one delivery's result, pauses, and resumes the next over exactly where it left off.
Generators automatically implement the iterator protocol, so they can be used directly in for loops, passed to list(), or driven manually with next(). They are especially useful for working with large or infinite sequences without holding every value in memory at once.
Cricket analogy: A scorer tracking a Test match ball by ball doesn't need the entire five-day scorecard preloaded; like a for loop over a generator, they process each delivery live without storing every possible future ball in memory.
2. Syntax
def my_generator():
yield 1
yield 2
yield 3
for value in my_generator():
print(value)
gen = my_generator()
next(gen) # 1
next(gen) # 2
# Generator expression (like a list comprehension, but lazy)
squares = (x * x for x in range(5))3. Explanation
When a function contains a 'yield' statement, calling it does not run the function body immediately; instead it returns a generator object. Each call to next() on that generator runs the function until the next 'yield', returning that value and suspending execution with all local state preserved.
Cricket analogy: Selecting a bowler for the attack doesn't make them bowl instantly; they only deliver the ball when the umpire calls 'play', just as calling a generator function only creates the object, and next() actually advances execution to the next yield.
This laziness makes generators memory-efficient compared to building a full list: values are produced on demand instead of all at once. However, a generator can only be iterated over once; after it is exhausted (raises StopIteration), it cannot be reset and must be recreated by calling the generator function again.
Cricket analogy: Watching a match live instead of demanding a pre-recorded highlights reel of every ball saves storage, but once the live broadcast ends you can't rewind it - you'd need to start a fresh telecast, just as an exhausted generator raises StopIteration and must be recreated.
Generators are lazy and memory-efficient: they compute each value only when requested, unlike lists which store every element up front. This makes generators ideal for large datasets or infinite sequences, but remember that a generator exhausts after one full pass and cannot be reused.
4. Example
def countdown(n):
while n > 0:
yield n
n -= 1
print('Liftoff!')
for value in countdown(3):
print(value)
gen = countdown(2)
print(next(gen))
print(next(gen))
try:
print(next(gen))
except StopIteration:
print('Generator exhausted')
squares = (x * x for x in range(4))
print(list(squares))5. Output
3
2
1
Liftoff!
2
1
Liftoff!
Generator exhausted
[0, 1, 4, 9]6. Key Takeaways
- 'yield' turns a function into a generator function that returns a generator object when called.
- Each call to next() resumes execution right after the last 'yield', preserving local variables.
- Generators are lazy: values are produced on demand, making them memory-efficient for large or infinite sequences.
- A generator exhausts after one complete pass and cannot be restarted; call the generator function again for a new one.
- Generator expressions '(expr for item in iterable)' provide a compact, lazy alternative to list comprehensions.
Practice what you learned
1. What keyword distinguishes a generator function from a regular function?
2. What is the main memory advantage of generators over lists?
3. What happens when you call next() on a generator that has already been fully exhausted?
4. What happens immediately when you call a generator function (one containing yield)?
5. What is a generator expression syntactically similar to?
Was this page helpful?
You May Also Like
Iterators in Python
Understand the iterator protocol in Python, including __iter__, __next__, and how for loops use iterators internally.
Decorators in Python
Learn how Python decorators use the @syntax to wrap functions, adding behavior like logging or timing without changing the original function's code.
List Comprehension in Python
A concise Python syntax for building lists from iterables in a single expression, including conditionals and the memory-difference vs generator expressions.
Lambda Functions in Python
Writing small anonymous functions with lambda, using them with map/filter/sorted, and avoiding the late-binding closure trap in loops.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics