7 min read
ā¢Question 21 of 41mediumIterators in Python
Creating custom iterators.
What You'll Learn
- What iterators and iterables are
- The iterator protocol (iter and next)
- Creating custom iterators
- Built-in iterator functions
- The itertools module for advanced iteration
Understanding Iterators vs Iterables
An iterable is any object that can return an iterator (has __iter__). An iterator is an object that produces values one at a time (has __next__).
code.pyPython
# Lists are iterables, not iterators
my_list = [1, 2, 3]
print(hasattr(my_list, '__iter__')) # True
print(hasattr(my_list, '__next__')) # False
# Get an iterator from the iterable
iterator = iter(my_list)
print(hasattr(iterator, '__next__')) # True
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# next(iterator) # Raises StopIterationThe Iterator Protocol
To create a custom iterator, implement __iter__ and __next__:
code.pyPython
class Counter:
"""Iterator that counts from start to end (exclusive)."""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self # Iterator returns itself
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += 1
return value
# Using the iterator
for num in Counter(1, 5):
print(num) # 1, 2, 3, 4
# Manual iteration
counter = Counter(10, 13)
print(next(counter)) # 10
print(next(counter)) # 11
print(next(counter)) # 12Separate Iterator Pattern
For reusable iterables, separate the iterator from the iterable:
code.pyPython
class Team:
"""Iterable that can be iterated multiple times."""
def __init__(self, members):
self.members = members
def __iter__(self):
return TeamIterator(self.members)
class TeamIterator:
def __init__(self, members):
self.members = members
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.members):
raise StopIteration
member = self.members[self.index]
self.index += 1
return member
team = Team(["Alice", "Bob", "Charlie"])
# Can iterate multiple times
for member in team:
print(member)
for member in team: # Works again!
print(member)Built-in Iterator Functions
Python provides powerful built-in functions for iteration:
code.pyPython
nums = [1, 2, 3, 4, 5]
# enumerate - get index and value
for i, num in enumerate(nums, start=1):
print(f"{i}. {num}") # 1. 1, 2. 2, etc.
# zip - iterate in parallel
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# zip_longest for unequal lengths
from itertools import zip_longest
for a, b in zip_longest([1, 2], [1, 2, 3], fillvalue=0):
print(a, b) # (1,1), (2,2), (0,3)
# reversed - iterate backwards
for num in reversed(nums):
print(num) # 5, 4, 3, 2, 1
# sorted - iterate in sorted order
for num in sorted(nums, reverse=True):
print(num) # 5, 4, 3, 2, 1The itertools Module
Advanced iteration patterns:
code.pyPython
from itertools import (
count, cycle, repeat, # Infinite iterators
chain, islice, # Chaining and slicing
permutations, combinations, # Combinatorics
groupby # Grouping
)
# Infinite iterators (use with caution!)
for i in count(10): # 10, 11, 12, ...
if i > 12:
break
print(i)
# Cycle through items indefinitely
colors = cycle(['red', 'green', 'blue'])
for _ in range(5):
print(next(colors)) # red, green, blue, red, green
# Chain multiple iterables
combined = chain([1, 2], [3, 4], [5, 6])
print(list(combined)) # [1, 2, 3, 4, 5, 6]
# Slice an iterator
first_five = islice(count(), 5)
print(list(first_five)) # [0, 1, 2, 3, 4]
# Combinations and permutations
print(list(combinations('ABC', 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'C')]
print(list(permutations('AB')))
# [('A', 'B'), ('B', 'A')]
# Group consecutive items
data = [('a', 1), ('a', 2), ('b', 3), ('b', 4)]
for key, group in groupby(data, key=lambda x: x[0]):
print(key, list(group))
# a [('a', 1), ('a', 2)]
# b [('b', 3), ('b', 4)]Iterators vs Generators
| Feature | Iterator | Generator |
|---|---|---|
| Definition | Class with iter/next | Function with yield |
| State | Explicit instance variables | Implicit (Python handles) |
| Memory | Instance overhead | Minimal |
| Use Case | Complex iteration logic | Simple value sequences |
code.pyPython
# Iterator version
class Squares:
def __init__(self, n):
self.n = n
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i >= self.n:
raise StopIteration
result = self.i ** 2
self.i += 1
return result
# Generator version (simpler!)
def squares(n):
for i in range(n):
yield i ** 2
# Both produce the same result
print(list(Squares(5))) # [0, 1, 4, 9, 16]
print(list(squares(5))) # [0, 1, 4, 9, 16]Interview Tip
When asked about iterators:
- Explain iterables (have iter) vs iterators (have next)
- Know that for loops call iter() then next() repeatedly
- Mention StopIteration signals the end
- Recommend generators for simple cases
- Know key itertools functions: chain, islice, groupby