Python Custom Iterables
A custom iterable is a class that defines __iter__ for looping.
Delegate to the inner list
The __iter__ method returns the inner list's iterator. The playlist writes no stepping code.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __iter__(self):
return iter(self.songs)
playlist = Playlist(["Morning Light", "City Rain"])
for song in playlist:
print(song)Each walk starts fresh
Every iter() call asks the list for a new iterator. Two walks both see every song.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __iter__(self):
return iter(self.songs)
playlist = Playlist(["Morning Light", "City Rain"])
print(list(playlist))
print(list(playlist))The __getitem__ fallback
With no __iter__, Python tries indexes 0, 1, and so on. An IndexError from the inner list ends the walk.
class Shelf:
def __init__(self, books):
self.books = books
def __getitem__(self, index):
return self.books[index]
shelf = Shelf(["Red Kite", "Blue Door"])
for book in shelf:
print(book)Unpack and spread your class
Unpacking walks your iterable once. The * spread collects its items into a new list.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __iter__(self):
return iter(self.songs)
playlist = Playlist(["Morning Light", "City Rain"])
first, second = playlist
print(first)
print(second)
print([*playlist, "Encore"])Example
class Playlist:
def __init__(self, title):
self.title = title
self.songs = []
def add(self, song):
self.songs.append(song)
def __iter__(self):
return iter(self.songs)
def __len__(self):
return len(self.songs)
def __contains__(self, song):
return song in self.songs
playlist = Playlist("Road Trip")
playlist.add("Morning Light")
playlist.add("City Rain")
playlist.add("Night Drive")
print(playlist.title)
print(len(playlist))
for song in playlist:
print(song)
print("City Rain" in playlist)
first, second, third = playlist
print(first)
print(third)
print([*playlist, "Encore"])Key points
- Container: an object that holds other objects, like a playlist holding songs
- Use __iter__ to delegate iteration to a collection stored inside your object
- Delegation: letting another object do the work and returning its result
- Use unpacking to split an iterable into named variables
- Use * in a list display to spread an iterable into a new list
- A custom iterable loops because its class defines __iter__.
- No separate iterator class is needed.
In this example, Playlist stores its songs in a plain list. The add method appends one song to that list. The __iter__ method delegates by returning iter(self.songs). The loop therefore reuses the list's own stepping code. The __len__ and __contains__ methods delegate in the same spirit. Unpacking walks the playlist and names its three songs. The final line spreads the playlist into a new list and adds one extra song.
Note
The old __getitem__ fallback still works without __iter__.
Prefer __iter__, because its intent is clear and its errors are cleaner.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
How does a container class delegate iteration to an inner list named self.songs?