Advanced Python
Python Iterators
Key Definitions
| Term | Definition |
|---|---|
| Iterable | Any object that can be looped through (list, tuple, set, dict, string, map, filter, etc.) |
| Iterator | A special object used to retrieve values from an iterable one at a time |
iter(obj) |
Returns the iterator for an iterable object |
next(iterator) |
Returns the next value from the iterator |
StopIteration |
Exception raised when the iterator has no more values |
What Actually Happens in a for Loop
Every for loop secretly uses an iterator under the hood:
x = [1, 2, 3]
# What you write:
for val in x:
print(val)
# What Python actually does:
x_iter = iter(x) # calls x.__iter__()
while True:
try:
val = next(x_iter) # calls x_iter.__next__()
print(val)
except StopIteration:
break
iter() and next() Functions
x = [1, 2, 3]
x_iter = iter(x)
print(next(x_iter)) # 1
print(next(x_iter)) # 2
print(next(x_iter)) # 3
print(next(x_iter)) # ❌ StopIteration
iter(x)→ returns alist_iteratorobject (not the list itself)next()must be called on the iterator, not the original object
next(x) # ❌ TypeError: list object is not an iterator
next(iter(x)) # ✅ 1
Dunder Methods Behind the Scenes
| Function | Dunder method called |
|---|---|
iter(obj) |
obj.__iter__() |
next(iterator) |
iterator.__next__() |
x_iter = x.__iter__() # same as iter(x)
x_iter.__next__() # same as next(x_iter)
What Makes an Object Iterable?
- Has a
__iter__method → it is iterable - Has a
__next__method → it is an iterator
Creating a Custom Iterator — Two Classes
The clean approach: one class for the iterable, a separate class for the iterator.
class Numbers:
def __init__(self, num1, num2, num3):
self.num1 = num1
self.num2 = num2
self.num3 = num3
def __iter__(self):
return NumberIterator(self.num1, self.num2, self.num3)
class NumberIterator:
def __init__(self, one, two, three):
self.one = one
self.two = two
self.three = three
self.current = 0
def __next__(self):
self.current += 1
if self.current == 1:
return self.one
elif self.current == 2:
return self.two
elif self.current == 3:
return self.three
else:
raise StopIteration
nums = Numbers(1, 2, 3)
for num in nums:
print(num) # 1 2 3
Creating a Custom Iterator — One Class
You can combine the iterable and iterator into a single class by defining both __iter__ and __next__:
class Numbers:
def __init__(self, num1, num2, num3):
self.num1 = num1
self.num2 = num2
self.num3 = num3
self.current = 0
def __iter__(self):
self.current = 0 # reset on each new iteration
return self # return the object itself as the iterator
def __next__(self):
self.current += 1
if self.current == 1:
return self.num1
elif self.current == 2:
return self.num2
elif self.current == 3:
return self.num3
else:
raise StopIteration
nums = Numbers(1, 2, 3)
for num in nums:
print(num) # 1 2 3
⚠️ One-Class Approach Limitation — Shared State
When __iter__ returns self, all iterators from the same object share the same internal state:
nums = Numbers(1, 2, 3)
iter1 = iter(nums)
iter2 = iter(nums) # resets current to 0 on the SAME object
print(next(iter1)) # 1
# creating iter2 reset current, so:
print(next(iter1)) # 1 again — not 2!
Why: iter1 and iter2 are literally the same object (nums). Creating iter2 reset current = 0, which affects iter1.
Two-class approach avoids this ✅
iter1 = iter(nums) # returns a NEW NumberIterator instance
iter2 = iter(nums) # returns a different NEW NumberIterator instance
# They have completely independent internal state
Summary — Two Approaches
| Two-class approach | One-class approach | |
|---|---|---|
__iter__ returns |
A new iterator object | self |
| Independent iterators | ✅ Yes | ❌ No — shared state |
| Complexity | Slightly more code | Simpler |
| Best practice | ✅ Preferred | ⚠️ Use with caution |
Key Takeaways & Recap
| Concept | Summary |
|---|---|
| Iterator | Object with __next__ — returns the next value or raises StopIteration |
| Iterable | Object with __iter__ — returns an iterator |
for loop |
Calls iter() then next() repeatedly until StopIteration |
StopIteration |
Signals the iterator is exhausted — for loop catches this and stops |
| Two-class design | Preferred — iterable and iterator have separate, independent instances |
| One-class design | Simpler but all calls to iter() return the same object — shared state |