A generator works like a ticket machine at a stadium: instead of printing all tickets for ten thousand fans at once, it prints one when you ask and waits until you request the next. In Python, this means using
yield inside a function to pause execution and hand back values one at a time.The Mental Model
def count_up(n):
for i in range(1, n + 1):
yield i
g = count_up(3)
print(next(g)) # Prints 1. Function pauses at 'yield'.
print(next(g)) # Prints 2. Resumes where it left off.
print(list(g)) # Prints [3]. Finishes the loop.Notice that
count_up returns a generator object, not the numbers themselves. The code inside does not run until you ask for values by calling next() or looping over it. This is lazy evaluation: work happens only when needed.Why Lazy Evaluation Matters
def temp_readings(n):
# Imagine reading from a slow sensor
for i in range(1, n + 1):
yield 20.5 + (i * 0.1)
total = sum(temp_readings(5))
print(total)The example above calculated the sum without storing five temperatures simultaneously. This matters when processing large log files or streaming data, where holding everything in a list would crash your memory.
Generator Expressions
You can write a one-line generator using parentheses
( ) instead of square brackets [ ]. It looks like a list comprehension but produces values lazily. This is useful for chaining operations without creating intermediate lists.# Eager: creates [1, 4, 9] in memory
squares_list = [x * x for x in range(1, 4)]
print(squares_list)
# Lazy: produces values one by one on demand
lazy_squares = (x * x for x in range(1, 4))
print(next(lazy_squares)) # Prints 1
print(list(lazy_squares)) # Prints [4, 9]def one_shot():
yield "first"
yield "second"
seq = one_shot()
print(list(seq)) # ['first', 'second']
print(list(seq)) # [] - Empty! It's exhausted.