A generator produces values one at a time instead of building the whole sequence in memory first.
The key word is yield. When Python reaches yield, it gives one value back and pauses the function. The next time Python asks for a value, execution continues from where it paused.
Start with the smallest generator
def count_to_three():
yield 1
yield 2
yield 3
for number in count_to_three():
print(number)
Output:
1
2
3
The function does not return a normal list. Calling it creates a generator object that can produce the next value when needed.
Video 1: Generators and their benefits
return vs. yield
return finishes a function. yield produces a value and pauses the generator so it can continue later.
def normal_function():
return 10
def generator_function():
yield 10
normal_function() gives you 10. generator_function() gives you a generator that can later yield 10.
Use next() to see the pause
def letters():
yield "A"
yield "B"
yield "C"
items = letters()
print(next(items))
print(next(items))
print(next(items))
Each call to next() resumes the generator until the next yield.
Video 2: Generators explained step by step
next(), generator functions, and generator comprehensions.Generate values with a loop
def squares(limit):
for number in range(limit):
yield number * number
for value in squares(5):
print(value)
Output:
0
1
4
9
16
This is where generators become practical. Python can calculate each square only when the loop asks for it.
Why generators can save memory
A list comprehension creates every result immediately:
squares_list = [n * n for n in range(1_000_000)]
A generator expression can produce those values lazily:
squares_generator = (n * n for n in range(1_000_000))
The generator does not need to create one million squared integers before you start consuming them.
Video 3: Lazy sequences and generator pipelines
A practical file-processing pattern
def nonempty_lines(path):
with open(path, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line:
yield line
This pattern can process a file line by line instead of first copying every line into a large list.
A generator is usually consumed once
numbers = (n for n in range(3))
print(list(numbers))
print(list(numbers))
Output:
[0, 1, 2]
[]
The first conversion consumes the generator. If you need to iterate again, create a new generator.
How this connects to the previous lesson
OSPython.018: Decorators Basics showed that Python functions can be passed around and wrapped. Generators add another important function behavior: a function can pause and resume while producing a sequence of values.
Common beginner mistakes
- Expecting a generator function call to immediately produce all its values.
- Using
returnwhen you mean to produce multiple values over time. - Calling
next()after the generator is exhausted without handlingStopIteration. - Trying to reuse an already-consumed generator.
- Converting a generator to a list immediately when lazy processing was the reason for using it.
Quick practice
- Create a generator named
even_numbers(limit). - Loop from
0tolimit - 1. - Use
yieldonly when the number is even. - Print the values with a
forloop. - Create another generator with a generator expression.
- Call
next()twice so you can see the generator advance one value at a time.
Key takeaway
yield turns a function into a generator function. Generators produce values on demand, remember where they paused, and are especially useful when you do not need an entire sequence stored in memory at once.

Leave a comment