Iterators

Learn about iterators in Python, the iterator protocol, creating custom iterators, and how iterators differ from iterables.

Ali Berro

By Ali Berro

8 min read Section 1
From: Python Fundamentals: From Zero to Hero

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.

iterables.py
# All of these are iterables
my_list = [1, 2, 3]
my_string = "Python"
my_dict = {"a": 1, "b": 2}
# You can iterate over them
for 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, b

Iterators

An iterator is an object that produces values one at a time. You get an iterator from an iterable using the iter() function:

iterators.py
my_list = [1, 2, 3]
iterator = iter(my_list) # Get iterator from iterable
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# print(next(iterator)) # StopIteration exception

The Iterator Protocol

The iterator protocol consists of two methods:

  1. __iter__(): Returns the iterator object itself
  2. __next__(): Returns the next value from the iterator, or raises StopIteration when done

How for Loops Work

When you use a for loop, Python automatically:

  1. Calls iter() on the iterable to get an iterator
  2. Calls next() repeatedly until StopIteration is raised
  3. Handles the StopIteration exception
for-loop-behind-scenes.py
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:
break

Creating Custom Iterators

You can create your own iterators by implementing the iterator protocol in a class:

Basic Iterator Class

custom-iterator.py
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
# Usage
counter = CountDown(5)
for num in counter:
print(num) # 5, 4, 3, 2, 1
# Or manually
counter = CountDown(3)
print(next(counter)) # 3
print(next(counter)) # 2
print(next(counter)) # 1
# print(next(counter)) # StopIteration

Iterator with Custom Range

range-iterator.py
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
# Usage
for 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, 2

Iterator for Fibonacci Sequence

fibonacci-iterator.py
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
# Usage
fib = Fibonacci(10)
for num in fib:
print(num) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Separating Iterable and Iterator

Sometimes it’s useful to separate the iterable from the iterator. This allows multiple independent iterations:

separate-iterable-iterator.py
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 iterators
squares = Squares(5)
for num in squares:
print(num) # 0, 1, 4, 9, 16
# Create another iterator
for 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()

iter-next.py
my_list = [10, 20, 30]
iterator = iter(my_list)
print(next(iterator)) # 10
print(next(iterator)) # 20
print(next(iterator)) # 30
# Default value for next()
iterator = iter([1, 2])
print(next(iterator, "No more items")) # 1
print(next(iterator, "No more items")) # 2
print(next(iterator, "No more items")) # No more items (default)

enumerate()

Returns an iterator of tuples containing index and value:

enumerate.py
items = ['apple', 'banana', 'cherry']
for index, value in enumerate(items):
print(f"{index}: {value}")
# 0: apple
# 1: banana
# 2: cherry
# With start parameter
for index, value in enumerate(items, start=1):
print(f"{index}: {value}")
# 1: apple
# 2: banana
# 3: cherry

zip()

Combines multiple iterables into an iterator of tuples:

zip.py
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 shortest
list1 = [1, 2, 3]
list2 = ['a', 'b']
for item in zip(list1, list2):
print(item) # (1, 'a'), (2, 'b')

reversed()

Returns a reverse iterator:

reversed.py
my_list = [1, 2, 3, 4, 5]
for item in reversed(my_list):
print(item) # 5, 4, 3, 2, 1
# Works with strings too
for char in reversed("Python"):
print(char) # n, o, h, t, y, P

Exercises

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

Checks: 0 times
Answer:
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()

Checks: 0 times
Answer:
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

Checks: 0 times
Answer:
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()

Checks: 0 times
Answer:
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()

Checks: 0 times
Answer:
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")

Course Progress

Section 53 of 61

Back to Course