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.
Before this lesson: Python for Loops, Python List Comprehensions
2Python Iterators: diagram
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():
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:
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:
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:
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:
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 doesraise StopIterationwhen there are no more.__iter__(self)returns the iterator itself (return self), so thatforanditer()accept it.
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:
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":
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
forloop does it all. - Want one item by hand, such as a header line or the first match?
next(it)ornext(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
- Python Iterator vs Generator: What Is the Difference? — An **iterator** is any object that returns one item at a time through `__next__()`, and a **generator** is a …
- Python reversed() Function — `reversed(seq)` returns an **iterator** that walks a sequence from the last item to the first, without changi…
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.
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.
- Line 1
Countdownis an iterator class: it will hand out numbers one at a time. - Line 2-3
__init__stores the starting number inself.remaining. This attribute is the iterator's memory of where it is. - Line 5-6
__iter__returnsself. The object is its own iterator, which is what letsfor,list()anditer()accept it. - Line 8-10Python calls
__next__whenever it wants another item. Whenremaininghas reached 0 there is nothing left, so the method doesraise StopIteration, the signal for "finished". - Line 11-13Otherwise the method remembers the current number in
value, counts down by one ready for next time, and returns the remembered number. - Line 15Creating
Countdown(3)runs only__init__. No number has been handed out yet. - Line 16The first
list(rocket)calls__next__untilStopIterationand collects[3, 2, 1]. By thenremainingis 0, so the secondlist(rocket)meetsStopIterationat 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))✓ 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))✓ 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)✓ 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)✓ 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())✓ 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 ✓ CompletedWrite 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.
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"))
iter(items) gives you an iterator, and next(iterator, default) returns the default instead of raising StopIteration when the iterator is empty.def first_or_default(items, default=None):
return next(iter(items), default)
print(first_or_default(["tea", "coffee"], "nothing"))
print(first_or_default([], "nothing"))
2. Predict what is left
Predict the output Easy ✓ CompletedRead 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.
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"))
break ends the loop, but it does not put the item back or rewind the iterator. Count which items the loop has already taken: 10 and 20. Then next() continues from there.30
90
empty
2 more exercises → 6 more examples
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