Generators are iterators PREMIUM

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
3 min. read Watch as video Python 3.9—3.13
Python Morsels
Watch as video
03:00

Generators are a type of "iterator".

Iterators are single-use lazy iterables

We have a generator object here (squares):

>>> numbers = [2, 1, 3, 4, 7]
>>> squares = (n**2 for n in numbers)
>>> squares
<generator object <genexpr> at 0x7f66fcc40f20>

There are two things that you can do with generator objects.

You can pass generators to the built-in next function to compute just the next item in them:

>>> next(squares)
4
>>> next(squares)
1

Or you can loop over them (with a for loop, the list constructor, or any other form of looping).

>>> list(squares)
[9, 16, 49]

But you can only loop over them once:

>>> list(squares)
[]

Generators are lazy single-use iterables:

  1. They're lazy because they do work as you loop over them.

  2. They're single-use because they can only be looped over once.

Generators are iterators and iterators are also lazy single-use iterables. In fact, every fact we just saw about generators is also true of iterators.

The same way that you can think of a sequence as a list-like object, you can think of an iterator as a generator-like object. A list is a kind of sequence. A generator is a kind of iterator.

Aside: that style of thinking noted in the last sentence of X-like object is part of Python's philosophy of duck typing.

Files are iterators

Generators aren't the only iterator in Python. File objects are also iterators.

Let's take this colors.txt file:

purple
green
blue
pink

And open it using the built-in open function to get a file object:

>>> my_file = open('colors.txt')

We can pass this file object to next to get its next line from disk:

>>> next(my_file)
'purple\n'
>>> next(my_file)
'green\n'

We can also loop over this file object to get each line in the file:

>>> for line in my_file:
...     print(line)
...
blue

pink

But those lines are retrieved from disk lazily. Python doesn't get the line until the point that we ask for it.

Helper functions return an iterator

Almost every looping helper function in Python also returns an iterator. For example enumerate returns an iterator.

We normally use enumerate by immediately looping over it:

>>> letters = "Python!"
>>> for n, letter in enumerate(letters, start=1):
...     print(n, letter)
...
1 P
2 y
3 t
4 h
5 o
6 n
7 !

When we loop over enumerate we'll get a 2-item tuple of numbers counting upward along with each item from the iterable that we passed to enumerate.

What if instead of immediately looking over the return value of enumerate, we stored it in a variable?

>>> e = enumerate(letters, start=1)

When we call enumeurate we'll get an enumerate object (aside: enumerate is actually a class):

>>> e
<enumerate object at 0x7f7d3c5afe40>

This enumerate object acts like an iterator.

We can pass this enumerate object to the built-in next function to get just the next item from it:

>>> next(e)
(1, 'P')
>>> next(e)
(2, 'y')

Or we can loop over it fully to get everything within it:

>>> list(e)
[(3, 't'), (4, 'h'), (5, 'o'), (6, 'n'), (7, '!')]
>>> list(e)
[]

But if we loop over this enumerate object a second time, we'll see there's nothing left in it :

>>> list(e)
[]

An enumerate object does work as we loop over it. It delays the work of computing the next item until it's asked for its next item.

The enumerate function gives us back an iterator. And so does zip and so does reversed and so does everything in Python's itertools module:

>>> import itertools

Pretty much all of Python's looping helper functions return iterator objects.

Aside: a notable exception is range, which returns an object that isn't an iterator.

Summary

So generators are iterators and iterators are lazy, single-use iterables which do work as you're looping over them. Iterators compute their next value as you ask for it.

Iterators can be passed to the built-in next function or they can be looped over, but just once (because they're single-use iterables).

Series: Generator Expressions

List comprehensions make new lists. Generator expressions make new generator objects. Generators are iterators, which are lazy single-use iterables. Unlike lists, generators aren't data structures. Instead they do work as you loop over them.

To track your progress on this Python Morsels topic trail, sign in or sign up.

0%
Python Morsels
Watch as video
03:00
This is a free preview of a premium screencast. You have 2 previews remaining.