Iterators
An iterator is an object that implements the iterator protocol, allowing you to traverse through a sequence of values one at a time. Unlike lists or tuples that store all values in memory, iterators generate values on-demand, making them memory-efficient for large datasets. Understanding iterators unlocks powerful patterns for custom iteration behavior!
Iterables vs Iterators
An iterable is any object you can iterate over (lists, tuples,
strings, dicts). An iterator is an object that produces the next
value when you call next() on it. When you use a for
loop, Python automatically converts the iterable into an iterator.
Click Run to execute your code
- Iterable: Has
__iter__() method that returns an
iterator- Iterator: Has both
__iter__() (returns self) and
__next__() (returns next value)All iterators are iterables, but not all iterables are iterators. Lists are iterables but not iterators until converted.
The Iterator Protocol
To create a custom iterator, you must implement two methods: __iter__()
and __next__(). The __iter__() method returns the
iterator object itself (or a new iterator object). The __next__()
method returns the next value and raises StopIteration when there are
no more items.
Click Run to execute your code
iter() function to
get an iterator from any iterable, and next() to manually advance
through values. This gives you fine-grained control over iteration!
Creating Custom Iterators
Custom iterators let you define exactly how iteration works for your objects. This is powerful for lazy evaluation, infinite sequences, or custom traversal logic.
Click Run to execute your code
Memory Efficiency of Iterators
Unlike lists that store all values in memory, iterators generate values on-demand. This makes them perfect for large datasets or infinite sequences that wouldn't fit in memory.
Click Run to execute your code
StopIteration), you can't reuse it. To iterate again, you need to
create a new iterator by calling __iter__() or using
iter() again. This is why you can only iterate over a file handle
once!
Common Mistakes
1. Forgetting to raise StopIteration
# Wrong - will loop forever or return None
class Counter:
def __iter__(self):
self.count = 0
return self
def __next__(self):
if self.count < 5:
self.count += 1
return self.count
# Missing: raise StopIteration
# Correct - explicitly raise StopIteration
class Counter:
def __iter__(self):
self.count = 0
return self
def __next__(self):
if self.count < 5:
self.count += 1
return self.count
raise StopIteration # Signal end of iteration
2. Trying to iterate over an exhausted iterator
# Wrong - iterator is exhausted after first loop
numbers = iter([1, 2, 3])
for n in numbers:
print(n)
for n in numbers: # This won't print anything!
print(n)
# Correct - create a new iterator for each loop
numbers = [1, 2, 3]
for n in numbers: # Automatically creates iterator
print(n)
for n in numbers: # Creates a new iterator
print(n)
3. Confusing iterables with iterators
# Wrong - list is iterable but not an iterator
my_list = [1, 2, 3]
next(my_list) # TypeError: 'list' object is not an iterator
# Correct - convert to iterator first
my_list = [1, 2, 3]
my_iterator = iter(my_list)
print(next(my_iterator)) # Works: 1
print(next(my_iterator)) # Works: 2
Task: Create a countdown iterator that counts down from a given number to 1, then raises StopIteration.
Requirements:
- Implement a
Countdownclass with__iter__and__next__methods - Initialize with a starting number
- Return numbers in descending order (start, start-1, ..., 1)
- Raise
StopIterationwhen countdown reaches 0 - Test it with both a for loop and manual
next()calls
Click Run to execute your code
Show Solution
class Countdown:
def __init__(self, start):
self.start = start
self.current = start
def __iter__(self):
self.current = self.start # Reset for new iteration
return self
def __next__(self):
if self.current > 0:
value = self.current
self.current -= 1
return value
else:
raise StopIteration
# Test with for loop
print("Countdown from 5:")
for num in Countdown(5):
print(num)
# Test with manual next()
print("\nManual iteration from 3:")
counter = Countdown(3)
it = iter(counter)
print(next(it)) # 3
print(next(it)) # 2
print(next(it)) # 1
# next(it) would raise StopIteration
Summary
- Iterable: Object with
__iter__()that can be looped over (lists, tuples, strings, dicts) - Iterator: Object with both
__iter__()and__next__()that produces values on-demand - Iterator Protocol: Implement
__iter__()(returns self) and__next__()(returns next value, raises StopIteration when done) - Memory Efficiency: Iterators generate values lazily, perfect for large datasets
- One-time Use: Iterators are consumed after one pass; create new ones for multiple iterations
- Built-in Functions: Use
iter()to get an iterator from an iterable,next()to manually advance - StopIteration: Must raise this exception to signal end of iteration
What's Next?
Now you understand the iterator protocol! Next, we'll explore
generators, which provide a simpler way to create iterators using
the yield keyword. Generators are iterators under the hood, but
Python handles all the __iter__ and __next__ boilerplate
for you!
Enjoying these tutorials?