Warming up the neural circuits...
By the end of this chapter you will:
Every for loop in Python is protocol-driven. Python asks an object for an iterator, repeatedly calls next(), and stops only when StopIteration is raised.
When you understand this protocol, you can:
values = [10, 20, 30]
# What Python effectively does
it = iter(values)
while True:
try:
item = next(it)
print(item)
except StopIteration:
breakThe for statement is syntax sugar over iter() + next() + StopIteration.
items = [1, 2, 3] # iterable
it = iter(items) # iterator
print(next(it)) # 1
print(next(it))
class PageRange:
def __init__(self, start: int, end: int):
self.start = start
self.end = end
def __iter__(self):
current =
from functools import partial
with open("events.log", "rb") as f:
read_chunk = partial(f.read, 8)
for chunk in iter(read_chunk, b""):
print(def numbers():
yield 1
yield 2
it = numbers()
print(list(it)) # [1, 2]
print(list(it)) # [] exhausted
# Correct: create a fresh iterator
printIterable means "I can start a traversal." Iterator means "I am the traversal in progress." If you need replay, keep the iterable, not a consumed iterator.
| If you need... | Prefer |
|---|---|
| Repeatable traversal | Iterable container (list, tuple, custom class with __iter__) |
| One-pass streaming | Iterator / generator |
| Fixed-size chunk reading | iter(callable, sentinel) |
| Random indexing and length ops | Concrete sequence (list, tuple) |
| Mistake | Symptom | Fix |
|---|---|---|
Calling next() on a non-iterator | TypeError: 'list' object is not an iterator | Wrap first: it = iter(my_list) |
| Reusing consumed iterator | Second loop prints nothing | Recreate iterator from source iterable |
Returning self from __iter__ in reusable container without reset | leaks across loops | Return a fresh iterator object or generator |
Catching broad exceptions around next() | Hidden logic bugs | Catch only StopIteration at protocol boundary |
Materializing stream too early (list(generator)) | Memory blowups | Keep pipeline lazy until final sink |
for loop into explicit iter() and next() calls.nums = [2, 4, 6]Print 2, 4, 6 using next()obj can be int, str, list, dictTrue for iterable objects, False otherwisen = 44, 3, 2, 1Binary file path and chunk sizeLoop ends naturally at EOFFunction returning many recordsDecision based on replay, memory, and consumer behaviorBeginner:
"What is the difference between iterable and iterator in Python?"
An iterable can create a new iterator (
iter(obj)). An iterator produces values withnext()and is consumed over time.
"Why does a for loop stop automatically?"
Because
next()eventually raisesStopIteration, and the loop handles that internally.
Senior:
"When would you use iter(callable, sentinel) in production code?"
For chunked reading from files/sockets or polling-style APIs where a specific return value marks completion.
"How do you design a reusable iterable object without state bugs?"
Keep container state immutable and return a fresh iterator from
__iter__each time, instead of sharing mutable cursor state.
iter() + repeated next() calls.Need replay? -> iterable container
Need one-pass stream? -> iterator/generator
Loop internals? -> iter + next + StopIteration
Read until marker? -> iter(callable, sentinel)What stops a Python for loop internally?
| Type | Property |
|---|---|
| Iterable | Can produce a fresh iterator (iter(obj)) |
| Iterator | Produces values via next() and is single-use |
This is an iterable object because __iter__ returns an iterator (here, a generator).
Python keeps calling read_chunk() and stops when return value equals b"".
Exhaustion is not a bug; it is part of iterator semantics.