🎓 A1Learner
🔍
0 of 102 lessons completed

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?