Skip to main content

How to Enable Iteration on Custom Objects in Python

By default, custom Python classes are not iterable. If you try to run a for loop over an instance of a class you defined, Python will raise a TypeError unless you explicitly implement the Iterator Protocol.

This guide explains how to make your objects compatible with loops, list comprehensions, and unpacking by implementing the __iter__ and __next__ magic methods, or by using the simpler generator approach.

Understanding the Iterator Protocol

To make an object iterable, Python expects it to follow a specific contract consisting of two special methods:

  1. __iter__(self): This method initializes the iteration. It must return an iterator object (an object that implements __next__).
  2. __next__(self): This method returns the next value in the sequence. When there are no more items, it must raise the StopIteration exception to signal the loop to end.

If you try to iterate over a class without these, Python throws an error.

class Team:
def __init__(self, members):
self.members = members

my_team = Team(["Alice", "Bob", "Charlie"])

try:
# ⛔️ Error: The class does not support iteration yet
for member in my_team:
print(member)
except TypeError as e:
print(f"Error: {e}")

Output:

Error: 'Team' object is not iterable

Method 1: Implementing __iter__ and __next__

This is the explicit way to create an iterator. You define how the state changes with every step and explicitly signal when to stop.

In this example, we create a Countdown class that counts from a starting number down to zero.

class Countdown:
def __init__(self, start):
self.current = start

def __iter__(self):
# Returns the iterator object itself
return self

def __next__(self):
# 1. Check if we should stop
if self.current < 0:
raise StopIteration

# 2. Capture current value
num = self.current

# 3. Update state for the next call
self.current -= 1

# 4. Return value
return num

# ✅ Correct: The object is now an Iterator
timer = Countdown(3)

for sec in timer:
print(sec)

Output:

3
2
1
0
note

Iterator vs. Iterable: In the example above, the object is an Iterator because it implements __next__. However, once an iterator is exhausted (reaches 0), it cannot be looped over again. To allow multiple loops over the same object, __iter__ should return a new iterator instance rather than self.

Method 2: Using Generators (The Pythonic Way)

Implementing __next__ and managing internal state manually can be verbose. A more modern and readable approach is to turn __iter__ into a generator using the yield keyword.

Python automatically creates __next__ and handles the StopIteration logic for you when you use yield.

class Team:
def __init__(self, members):
self.members = members

# ✅ Correct: Using yield makes this much simpler
def __iter__(self):
for member in self.members:
yield member

my_team = Team(["Alice", "Bob", "Charlie"])

# Iteration 1
print("--- Loop 1 ---")
for member in my_team:
print(member)

# Iteration 2 (Works because __iter__ returns a fresh generator every time)
print("--- Loop 2 ---")
print(list(my_team))

Output:

--- Loop 1 ---
Alice
Bob
Charlie
--- Loop 2 ---
['Alice', 'Bob', 'Charlie']
tip

Use the Generator approach (yield) whenever possible. It requires less code, manages state automatically, and supports multiple passes over the data if implemented correctly.

Method 3: The Sequence Protocol (__getitem__)

Before the modern iterator protocol existed, Python used the Sequence Protocol. If __iter__ is missing, Python checks for __getitem__.

Python will try to access the object with index 0, then 1, then 2, and so on, until an IndexError is raised.

class Inventory:
def __init__(self):
self.items = ["Sword", "Shield", "Potion"]

# ✅ Correct: Allows iteration via indexing
def __getitem__(self, index):
return self.items[index]

bag = Inventory()

for item in bag:
print(f"Item: {item}")

Output:

Item: Sword
Item: Shield
Item: Potion

Conclusion

To enable iteration on your custom classes:

  1. Use Generator Syntax (__iter__ + yield): This is the most efficient and readable method for 90% of use cases.
  2. Use Iterator Protocol (__iter__ + __next__): Use this if you need fine-grained control over the internal state or are building a low-level custom iterator type.
  3. Use Sequence Protocol (__getitem__): Use this if your object naturally behaves like an ordered list or array that supports indexing.