Legends ofPythos
Claim your name
Objects and Classes

Your own iterators

Lesson 7 of 13

Watch the lesson1:10 ยท with Torsten
When you write a for loop, Python does more work than it looks like. It calls the special method __iter__() on your object to get an iterator. Then it repeatedly calls next(iterator) until that call raises StopIteration. This is how loops actually move through data.

Seeing under the hood

# Let's manually iterate over a list
my_list = [10, 20, 30]
iterator = iter(my_list)
print(next(iterator)) # Prints: 10
print(next(iterator)) # Prints: 20
print(next(iterator)) # Prints: 30
try:
    print(next(iterator)) # nothing left: raises StopIteration
except StopIteration:
    print('Done')
Manual iteration
To make your own class work in a for loop, you define two methods. The collection's __iter__() returns an iterator object. That iterator must have a __next__() method that returns the next value or raises StopIteration when it runs out.

The Countdown example

class CountDown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        # Return a fresh iterator each time we loop
        return CountdownIterator(self.start)

class CountdownIterator:
    def __init__(self, current):
        self.current = current

    def __iter__(self):
        return self              # an iterator is iterable too

    def __next__(self):
        if self.current < 1:
            raise StopIteration
        val = self.current
        self.current -= 1
        return val

countdown = CountDown(3)
print(list(countdown))   # [3, 2, 1]
print(list(countdown))   # [3, 2, 1] again: a fresh iterator each time
Custom iterator
Why separate classes? An iterator is used up after one pass. If __iter__ returned the same object every time, you couldn't loop over it twice. By creating a new iterator instance inside __iter__, each for loop gets its own independent state.

Your turn

0 of 3 solved

Exercise 1

+40 XP
Look inside a for loop. Call iter() on the list path to get an iterator, then call next() on it three times, storing the results in first, second and third. Then write drain(iterator), which calls next() in a while True loop, collecting what it gets in a list, and returns the list when StopIteration is raised (catch it with try and except).
path = ['gate', 'bridge', 'tower', 'hall']


first = second = third = None




def drain(iterator):
    # next() until StopIteration, then return what you collected
    pass

Run your code to check it against the tests.

Exercise 2

+40 XP
Write a class Countdown whose __init__(self, start) stores where to start. It is its own iterator: __iter__ returns self, and __next__ returns the current number and counts down, raising StopIteration after 1. So list(Countdown(3)) is [3, 2, 1]. Because it is its own iterator, a countdown is used up after one pass.
class Countdown:
    def __init__(self, start):
        pass


    # __iter__ and __next__ go here

Run your code to check it against the tests.

Exercise 3

+40 XP
Write a Playlist that can be looped over more than once. Playlist(songs) keeps the list, supports len(), and its __iter__ returns a new PlaylistIterator each time. PlaylistIterator(songs) hands the songs out one by one with __next__, raising StopIteration at the end, and its __iter__ returns itself.
class PlaylistIterator:
    # __init__, __iter__ and __next__
    pass




class Playlist:
    # __init__, __len__ and __iter__
    pass

Run your code to check it against the tests.