Iterators
Iterators are objects that allow you to traverse through a collection of items one at a time. They provide a way to access elements sequentially without needing to know the underlying data structure’s implementation details.
Iterables vs Iterators
Understanding the difference between iterables and iterators is crucial:
- Iterable: An object that can return an iterator (has
__iter__()method) - Iterator: An object that implements the iterator protocol (has
__iter__()and__next__()methods)
Iterables
An iterable is any object you can loop over. Common iterables include lists, tuples, strings, dictionaries, sets, and files.
# All of these are iterablesmy_list = [1, 2, 3]my_string = "Python"my_dict = {"a": 1, "b": 2}
# You can iterate over themfor item in my_list: print(item) # 1, 2, 3
for char in my_string: print(char) # P, y, t, h, o, n
for key in my_dict: print(key) # a, bIterators
An iterator is an object that produces values one at a time. You get an iterator from an iterable using the iter() function:
my_list = [1, 2, 3]iterator = iter(my_list) # Get iterator from iterable
print(next(iterator)) # 1print(next(iterator)) # 2print(next(iterator)) # 3# print(next(iterator)) # StopIteration exceptionThe Iterator Protocol
The iterator protocol consists of two methods:
__iter__(): Returns the iterator object itself__next__(): Returns the next value from the iterator, or raisesStopIterationwhen done
How for Loops Work
When you use a for loop, Python automatically:
- Calls
iter()on the iterable to get an iterator - Calls
next()repeatedly untilStopIterationis raised - Handles the
StopIterationexception
my_list = [1, 2, 3]
# This for loop:for item in my_list: print(item)
# Is equivalent to:iterator = iter(my_list)while True: try: item = next(iterator) print(item) except StopIteration: breakCreating Custom Iterators
You can create your own iterators by implementing the iterator protocol in a class:
Basic Iterator Class
class CountDown: def __init__(self, start): self.current = start
def __iter__(self): return self
def __next__(self): if self.current <= 0: raise StopIteration self.current -= 1 return self.current + 1
# Usagecounter = CountDown(5)for num in counter: print(num) # 5, 4, 3, 2, 1
# Or manuallycounter = CountDown(3)print(next(counter)) # 3print(next(counter)) # 2print(next(counter)) # 1# print(next(counter)) # StopIterationIterator with Custom Range
class MyRange: def __init__(self, start, stop, step=1): self.start = start self.stop = stop self.step = step self.current = start
def __iter__(self): return self
def __next__(self): if (self.step > 0 and self.current >= self.stop) or \ (self.step < 0 and self.current <= self.stop): raise StopIteration
value = self.current self.current += self.step return value
# Usagefor num in MyRange(1, 10, 2): print(num) # 1, 3, 5, 7, 9
for num in MyRange(10, 0, -2): print(num) # 10, 8, 6, 4, 2Iterator for Fibonacci Sequence
class Fibonacci: def __init__(self, limit=None): self.prev = 0 self.curr = 1 self.limit = limit self.count = 0
def __iter__(self): return self
def __next__(self): if self.limit and self.count >= self.limit: raise StopIteration
self.count += 1 value = self.prev self.prev, self.curr = self.curr, self.prev + self.curr return value
# Usagefib = Fibonacci(10)for num in fib: print(num) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34Separating Iterable and Iterator
Sometimes it’s useful to separate the iterable from the iterator. This allows multiple independent iterations:
class Squares: def __init__(self, max_value): self.max_value = max_value
def __iter__(self): return SquareIterator(self.max_value)
class SquareIterator: def __init__(self, max_value): self.current = 0 self.max_value = max_value
def __iter__(self): return self
def __next__(self): if self.current >= self.max_value: raise StopIteration
value = self.current ** 2 self.current += 1 return value
# Usage - can create multiple independent iteratorssquares = Squares(5)for num in squares: print(num) # 0, 1, 4, 9, 16
# Create another iteratorfor num in squares: print(num) # 0, 1, 4, 9, 16 (starts fresh)Built-in Iterator Functions
Python provides several built-in functions that work with iterators:
iter() and next()
my_list = [10, 20, 30]iterator = iter(my_list)
print(next(iterator)) # 10print(next(iterator)) # 20print(next(iterator)) # 30
# Default value for next()iterator = iter([1, 2])print(next(iterator, "No more items")) # 1print(next(iterator, "No more items")) # 2print(next(iterator, "No more items")) # No more items (default)enumerate()
Returns an iterator of tuples containing index and value:
items = ['apple', 'banana', 'cherry']
for index, value in enumerate(items): print(f"{index}: {value}")# 0: apple# 1: banana# 2: cherry
# With start parameterfor index, value in enumerate(items, start=1): print(f"{index}: {value}")# 1: apple# 2: banana# 3: cherryzip()
Combines multiple iterables into an iterator of tuples:
names = ['Alice', 'Bob', 'Charlie']ages = [25, 30, 35]
for name, age in zip(names, ages): print(f"{name} is {age} years old")# Alice is 25 years old# Bob is 30 years old# Charlie is 35 years old
# Different lengths - stops at shortestlist1 = [1, 2, 3]list2 = ['a', 'b']for item in zip(list1, list2): print(item) # (1, 'a'), (2, 'b')reversed()
Returns a reverse iterator:
my_list = [1, 2, 3, 4, 5]
for item in reversed(my_list): print(item) # 5, 4, 3, 2, 1
# Works with strings toofor char in reversed("Python"): print(char) # n, o, h, t, y, PExercises
Exercise 1: Basic Iterator
Create a class called CountUp that implements the iterator protocol. It should start at 0 and count up to a given limit (exclusive). Read the limit from input and print all numbers from the iterator.
Basic Iterator
class CountUp: def __init__(self, limit): self.current = 0 self.limit = limit
def __iter__(self): return self
def __next__(self): if self.current >= self.limit: raise StopIteration value = self.current self.current += 1 return value
limit = int(input())counter = CountUp(limit)for num in counter: print(num)Exercise 2: Using iter() and next()
Create a list with values [10, 20, 30, 40, 50]. Use iter() to get an iterator, then use next() three times to get the first three values. Print each value on a separate line.
Using iter() and next()
my_list = [10, 20, 30, 40, 50]iterator = iter(my_list)
print(next(iterator))print(next(iterator))print(next(iterator))Exercise 3: Custom Range Iterator
Create a class called MyRange that implements the iterator protocol. It should work like range(): take start, stop, and optional step (default 1). Read start, stop, and step from input, then iterate and print all values.
Custom Range Iterator
class MyRange: def __init__(self, start, stop, step=1): self.start = start self.stop = stop self.step = step self.current = start
def __iter__(self): return self
def __next__(self): if (self.step > 0 and self.current >= self.stop) or \ (self.step < 0 and self.current <= self.stop): raise StopIteration
value = self.current self.current += self.step return value
start = int(input())stop = int(input())step = int(input())
my_range = MyRange(start, stop, step)for num in my_range: print(num)Exercise 4: Using enumerate()
Read a number n, then read n strings. Use enumerate() to iterate over the strings and print each one with its index in the format “Index: value”.
Using enumerate()
n = int(input())items = []for i in range(n): items.append(input())
for index, value in enumerate(items): print(f"{index}: {value}")Exercise 5: Using zip()
Read two numbers n and m, then read n names and m ages. Use zip() to combine them and print each name with its age in the format “Name is Age years old”. If the lists have different lengths, only process the shorter length.
Using zip()
n = int(input())m = int(input())
names = []for i in range(n): names.append(input())
ages = []for i in range(m): ages.append(int(input()))
for name, age in zip(names, ages): print(f"{name} is {age} years old")