Python Iterators
Core Python Iteration and Files

Python Iterators

1Python Iterators: overview

An iterator is an object that hands out the items of a sequence one at a time through next(), remembers where it is, and raises StopIteration when nothing is left; an iterable is anything that can give you a fresh iterator when you call iter() on it.

GoalBy the end you can explain what a for loop does behind the scenes, use iter() and next() by hand, recognise an exhausted iterator, and write a class that is a one-shot iterator or a reusable iterable.

Before this lesson: Python for Loops, Python List Comprehensions

2Python Iterators: diagram

Python Iteratorsfor item in items:what you writeit = iter(items)called once, before the first passnext(it)ask for the next itema valueloop body runsitem holds the valuenothing leftStopIterationcaught by the loop: it ends quietlynext pass
A for loop calls iter() once to get an iterator, then calls next() before every pass. When next() raises StopIteration, the loop ends without showing an error.

3Python Iterators: explained step by step

In the generators lesson you met iter() and next() in passing. This lesson opens the box: how a for loop really asks for its items, what an iterator is, and how to build your own. Once you can see the protocol, questions like "why does this only work once?" stop being mysteries.

Iterable or iterator?

Two words, two different jobs:

  • An iterable is anything you can loop over: a list, a string, a dict, a range, a file. It can give you an iterator when you ask.
  • An iterator is the object that does the walking. It remembers its position and hands out the next item each time you call next() on it.

You ask an iterable for its iterator with the built-in iter():

Python
scores = [90, 75, 60]
it = iter(scores)

print(type(scores).__name__, type(it).__name__)
print(next(it))
print(next(it))
print(list(it))

The list never moves. It is just storage. The iterator is a separate small object that says "I am at position 2 of that list". That is why next(it) gives 90 and then 75, and why list(it) afterwards finds only the one item that is left.

What a for loop really does

for item in items: is shorthand for three steps. Python calls iter(items) once, calls next() on the result before every pass, and stops quietly when next() raises StopIteration. Written by hand, a loop looks like this:

Python
cart = ["milk", "bread", "eggs"]

it = iter(cart)
while True:
    try:
        item = next(it)
    except StopIteration:
        break
    print("Packing", item)
print("Done")

StopIteration is not an error here. It is the iterator's way of saying "I have nothing more". The for loop catches it for you, which is why you never see it in everyday code.

Iterators are used up

An iterator only moves forward. After it has handed out its last item it is exhausted and stays empty: it never rewinds. A list can create as many fresh iterators as you like, so looping over a list twice works. Reading one iterator twice does not:

Python
temps = iter([21, 25, 19])

print(list(temps))
print(list(temps))
print(sum(temps))

Everything that reads items uses them up: for, list(), sum(), max(), sorted(), the in operator and unpacking (a, b = it). A loop that ends early with break leaves the remaining items waiting for the next reader:

Python
queue = iter(["Asha", "Ben", "Chen", "Dee"])

for name in queue:
    print("Serving", name)
    if name == "Ben":
        break

print("Still waiting:", list(queue))

next() with a default

Calling next() on an exhausted iterator raises StopIteration. When running out is a normal possibility, pass a second argument. Python returns it instead of raising:

Python
pending = iter(["fix bug", "write tests"])
print(next(pending, "nothing left"))
print(next(pending, "nothing left"))
print(next(pending, "nothing left"))

Building your own iterator

This part uses a class, so look at the classes lesson first if __init__ and self are new to you. An object is an iterator when it has two methods:

  • __next__(self) returns the next item, or does raise StopIteration when there are no more.
  • __iter__(self) returns the iterator itself (return self), so that for and iter() accept it.
Python
class Evens:
    def __init__(self, limit):
        self.limit = limit
        self.number = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.number > self.limit:
            raise StopIteration
        value = self.number
        self.number += 2
        return value

print(list(Evens(10)))
print(*Evens(4))

The attributes limit and number are the iterator's memory, and that is exactly why it cannot be rewound. Why not just return a list? Because an iterator makes each item only when it is asked for, so it can describe a huge or even endless sequence, or data you cannot hold in memory at once, such as the lines of a big file. A generator function is a shortcut for writing the same thing in fewer lines; the class shows what happens underneath.

Make a class reusable: return a fresh iterator

A class whose __iter__ returns self is a one-shot iterator, like Evens above. Often you want something you can loop over again and again, like a list. Keep the data in the class and let __iter__ hand out a new iterator on every call:

Python
class Playlist:
    def __init__(self, songs):
        self.songs = songs

    def __iter__(self):
        return iter(self.songs)      # a brand-new iterator each time

mix = Playlist(["Intro", "Waves", "Outro"])
print(list(mix))
print(list(mix))
pairs = [(a, b) for a in mix for b in mix]
print(len(pairs))

Playlist is an iterable but not an iterator: it has no __next__. Every loop gets its own private walker, so even two nested loops over the same playlist (3 x 3 = 9 pairs) do not disturb each other.

iter(function, sentinel)

iter() has a second form. iter(function, stop_value) calls function() with no arguments again and again, hands out each result, and stops as soon as the result equals stop_value (the sentinel, which is not handed out). It is handy for "read until an end marker":

Python
import io

log = io.StringIO("start\nrunning\nEND\nignored\n")
for line in iter(log.readline, "END\n"):
    print(line.strip())

io.StringIO behaves like an open file that lives in memory. Notice log.readline has no parentheses: iter() needs the function itself so that it can call it repeatedly.

Which one do I need?

  • Just visiting every item? A plain for loop does it all.
  • Want one item by hand, such as a header line or the first match? next(it) or next(it, default).
  • Need to loop more than once? Keep a list, or build a class whose __iter__ returns a fresh iterator.
  • Need a lazy or endless sequence? Write an iterator class or a generator.

Quick reference: Python Iterators

4Python Iterators: syntax

it = iter(iterable)            # ask an iterable for an iterator
next(it)                       # next item, or raise StopIteration when empty
next(it, default)              # next item, or default when empty
iter(function, sentinel)       # call function() until it returns sentinel

class CountUp:                 # an iterator: its own memory, one pass only
    def __iter__(self):
        return self
    def __next__(self):
        ...                    # return the next value
        raise StopIteration    # when nothing is left

class Collection:              # a reusable iterable
    def __iter__(self):
        return iter(self.items)    # a fresh iterator on every call

5Python Iterators: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
class Countdown:
    def __init__(self, start):
        self.remaining = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining == 0:
            raise StopIteration
        value = self.remaining
        self.remaining -= 1
        return value

rocket = Countdown(3)
print(list(rocket), list(rocket))
Expected output[3, 2, 1] []

7Python Iterators: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 1Countdown is an iterator class: it will hand out numbers one at a time.
  2. Line 2-3__init__ stores the starting number in self.remaining. This attribute is the iterator's memory of where it is.
  3. Line 5-6__iter__ returns self. The object is its own iterator, which is what lets for, list() and iter() accept it.
  4. Line 8-10Python calls __next__ whenever it wants another item. When remaining has reached 0 there is nothing left, so the method does raise StopIteration, the signal for "finished".
  5. Line 11-13Otherwise the method remembers the current number in value, counts down by one ready for next time, and returns the remembered number.
  6. Line 15Creating Countdown(3) runs only __init__. No number has been handed out yet.
  7. Line 16The first list(rocket) calls __next__ until StopIteration and collects [3, 2, 1]. By then remaining is 0, so the second list(rocket) meets StopIteration at once and gives []: the iterator is used up.

8Python Iterators: common mistakes

Calling next() on a list

✗ Wrong

colors = ["red", "green", "blue"]
print(next(colors))
TypeError: 'list' object is not an iterator

✓ Right

colors = ["red", "green", "blue"]
it = iter(colors)
print(next(it))

A list is an iterable, not an iterator, so it has no current position to advance. Ask it for an iterator with iter() first and call next() on that.

Reading an iterator twice

✗ Wrong

scores = iter([72, 90, 65])
print(max(scores))
print(min(scores))
ValueError: min() iterable argument is empty

✓ Right

scores = [72, 90, 65]
print(max(scores))
print(min(scores))

max(scores) used up every item, so nothing is left for min(). Keep the data in a list when you need to read it more than once; only create an iterator when you want one pass.

Forgetting __iter__ in your own class

✗ Wrong

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

    def __next__(self):
        if self.remaining == 0:
            raise StopIteration
        self.remaining -= 1
        return self.remaining + 1

for n in Countdown(3):
    print(n)
TypeError: 'Countdown' object is not iterable

✓ Right

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

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining == 0:
            raise StopIteration
        self.remaining -= 1
        return self.remaining + 1

for n in Countdown(3):
    print(n)

A for loop starts by calling iter() on the object, and that needs __iter__. Having __next__ alone is not enough. Add __iter__ and return self.

Returning None instead of raising StopIteration

✗ Wrong

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

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining == 0:
            return None
        self.remaining -= 1
        return self.remaining + 1

for n in Countdown(3):
    print(n)
No error message: it prints 3, 2, 1 and then None again and again, and the loop never ends

✓ Right

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

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining == 0:
            raise StopIteration
        self.remaining -= 1
        return self.remaining + 1

for n in Countdown(3):
    print(n)

To the for loop, None is just another item. The only way to say "no more items" is raise StopIteration.

Calling the function you give to iter()

✗ Wrong

import io

log = io.StringIO("start\nEND\n")
for line in iter(log.readline(), "END\n"):
    print(line.strip())
TypeError: iter(v, w): v must be callable

✓ Right

import io

log = io.StringIO("start\nEND\n")
for line in iter(log.readline, "END\n"):
    print(line.strip())

log.readline() runs the method once and passes its result (a string) to iter(). Pass the method itself, without parentheses, so that iter() can call it again and again.

9Python Iterators: exercises

Write your answer, then press Check. The checks run in your browser.

1. First item or a default

Write code Easy

Write a function first_or_default(items, default=None) that returns the first item of any iterable (a list, a string, a dict, a generator ...) or default when there is no item at all.

Do it with iter() and next(), without a loop and without len(): a generator has no length, and asking for it would use it up.

Your code
def first_or_default(items, default=None):
    # get an iterator for items and take one item from it,
    # returning default when there is nothing to take
    pass

print(first_or_default(["tea", "coffee"], "nothing"))
print(first_or_default([], "nothing"))

2. Predict what is left

Predict the output Easy

Read the code and type exactly what it prints. Think about which items the for loop has already taken out of the iterator before it stops. Then press Check to run it and compare.

Read this code
numbers = iter([10, 20, 30, 40, 50])

for n in numbers:
    if n == 20:
        break

print(next(numbers))
print(sum(numbers))
print(next(numbers, "empty"))

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is the difference between an iterable and an iterator in Python?

An iterable is something you can loop over, such as a list, string or dict. It has an iter method that returns an iterator. An iterator is the object that keeps track of the position and hands out items through next. Iterators are also iterable (iter(it) returns itself), but they can be read only once, while a list can give you a new iterator every time.

Why is my iterator empty the second time I loop over it?

An iterator never rewinds. The first loop (or list(), sum(), unpacking ...) takes every item, and after that next() raises StopIteration straight away. map(), filter(), zip(), enumerate(), generators and open files behave the same way. Store the items in a list if you need them twice, or create a fresh iterator for each pass.

Should I write __iter__ and __next__ myself, or use a generator?

For most code a generator function with yield is shorter and less error-prone, because Python builds iter and next for you. Write the class form when the iterator needs several methods or extra state of its own, or when you want to understand exactly what the protocol does. To make a class reusable, you can even write iter itself as a generator function.

Further reading

10Next lesson

Up nextPython map, filter and reducemap() transforms every item of an iterable, filter() keeps only the items that pass a test, and functools.reduce() fold…

Related:
Python for LoopsPython enumerate() and zip()Python Generators, Iterators and yieldPython map, filter and reduce

← Python Generators, Iterators and yield · All Iteration and Files lessons · Practise in the IDE