Iterators and generators
An iterator is an object that hands you its values one at a time, and a generator is a function that writes one for you by using yield in place of return.
Last updated: 13 Oct, 2026 · Python 3.12
The Inheritance lesson built objects that share behaviour. This lesson is about objects that produce a sequence of values on demand, one value per request, without ever holding the whole sequence at once.
A list builds every value up front. An iterator does not. It keeps its place and produces the next value only when you ask for it. That is what lets a program read a file larger than memory, or print a model's reply one token at a time as each piece arrives.
[1, 2, 3, 4, 5, 6], turns it into an iterator with iter(), and pulls values with next() in a Jupyter notebook, where a cell's last value shows without print(). In a .py file, wrap the value in print(), as the runs below do.Syntax: iter() turns an iterable into an iterator; next() pulls the next value and raises StopIteration when there are none left.
it = iter([1, 2, 3, 4, 5, 6]) # an iterator over the list
first = next(it) # 1, the first value
# calling next again gives 2, then 3, and so on, then StopIterationPulling values with next
A list is an iterable: something you can loop over. iter() turns it into an iterator, which remembers how far you have read. Each next() returns the next value. A string is iterable too, so the same iter/next pair walks it one character at a time.
my_list = [1, 2, 3, 4, 5, 6]
it = iter(my_list) # a reader positioned at the start
print(type(it).__name__)
print(next(it))
print(next(it))
print(next(it))
# a string is iterable too: its iterator hands back one character at a time
my_string = "Hello"
chars = iter(my_string)
print(next(chars))
print(next(chars))list_iterator 1 2 3 H e
What the next calls produced
- list_iterator is the kind of object
iter()returned. It is not the list; it is a reader positioned at the start. - 1, 2, 3 came out one per
next()call, in order. The iterator moved its position forward each time, with 4, 5 and 6 still waiting. - H, e came from the string iterator the same way, proving
iterandnextwork on any iterable, not only lists.
When an iterator has nothing left, the next next() raises StopIteration. Catching it is how you know an iterator is spent; a for loop does this for you and stops.
it = iter([1, 2])
try:
print(next(it))
print(next(it))
print(next(it)) # the list is exhausted, so this raises
except StopIteration:
print("There are no elements in the iterator")1 2 There are no elements in the iterator
The first two next() calls returned 1 and 2. The third found nothing left and raised StopIteration, which the except caught. This is exactly what a for loop relies on to know when to stop.
Writing a generator with yield
Writing an iterator class by hand is work. A generator is the short way: an ordinary function that uses yield. Calling it does not run the body. It returns a generator object, and each value comes out at a yield when you ask for the next one. my_generator below yields three values in turn.
def my_generator():
yield 1 # hand back a value, then pause here until the next request
yield 2
yield 3def my_generator():
yield 1
yield 2
yield 3
gen = my_generator() # nothing has run yet; this is a generator object
print(type(gen).__name__)
for value in gen:
print(value)generator 1 2 3
Calling my_generator() built a generator object and ran none of the body. The for loop drove it: each pass asked for the next value, the function ran up to the next yield, handed back a number, and paused there until the next pass. After yield 3 the function ended, which is the generator's StopIteration, and the loop stopped.
Reading a large file lazily
The payoff is that a generator never has to hold everything at once. His read_large_file yields one line at a time, so a file of any size costs the same memory as a single line. The example writes a small file to a temporary folder first, then reads it and stops early to show that lines past the stop were never produced.
def read_large_file(file_path):
with open(file_path) as file:
for line in file: # the file yields one line at a time
yield line.rstrip("\n")import os, tempfile
# write a small five-line file to a temp folder so there is something to read back
path = os.path.join(tempfile.mkdtemp(), "log.txt")
with open(path, "w") as f:
for i in range(1, 6):
f.write(f"line {i}\n")
def read_large_file(file_path):
with open(file_path) as file:
for line in file:
yield line.rstrip("\n")
# stop at line 3 to prove lines 4 and 5 are never produced
for line in read_large_file(path):
print(line)
if line == "line 3":
breakline 1 line 2 line 3
The loop printed three lines and stopped at line 3. Lines 4 and 5 sat in the file and were never read, because the generator produces a line only when the loop asks. The same one-at-a-time delivery is how a chat interface prints a model's reply token by token: each token is yielded and shown as it arrives, instead of waiting for the whole answer.
List vs generator
| list | generator | |
|---|---|---|
| Values | all built up front | one at a time, on demand |
| Memory | holds every value | holds only its current place |
| Second pass | loop over it again | exhausted after one pass |
| Length | len() works | no len(), you consume it |
| Good for | small collections you reuse | large or streamed data |
Where iterators and generators show up in AI code
- Reading a dataset too large to fit in memory, one record or one line at a time.
- Streaming a model's reply, printing each token as it arrives instead of waiting for the full answer.
- Building a pipeline where each stage yields to the next, with no large intermediate list between them.
list(gen) and loop over that.Related
- Previous: Inheritance
- Next: Decorators
- Reference: Iterators and generators in the Python tutorial
- Change the stop line in the file reader to
if line == "line 5"and watch all five lines print. - After the
forloop overmy_generator(), loop a second time over the samegen. Nothing prints, because it is exhausted. - Turn the file reader into a token streamer: split each line on spaces and
yieldone word at a time.
This is what real progress feels like.