Programming and IT

Python: generators

A generator hands out values one at a time, and only when someone asks for them. It does not keep the whole sequence in memory, so it suits large files and endless streams. The price is that a generator is single-use: you cannot loop over it twice.

Updated
In this article

A function that pauses itself#

An ordinary function runs until return and forgets its state. A function with yield behaves differently: it hands out a value and freezes, keeping everything — where it stopped and its local variables — until the next request.

def countdown(n):
    while n > 0:
        yield n
        n -= 1
    print("done")

gen = countdown(3)
print(gen)          # <generator object countdown at 0x...>
print(next(gen))    # 3
print(next(gen))    # 2
print(next(gen))    # 1
next(gen)           # prints "done", then StopIteration

Calling countdown(3) does not run a single line of the body — it only creates a generator object. The first line runs on the first next. When the body reaches its end, the generator raises StopIteration; a for loop catches that exception itself, so you do not see it in ordinary code:

for x in countdown(3):
    print(x, end=" ")   # 3 2 1 done

Generator expressions#

If the logic fits in one expression, you do not need a separate function. The syntax is the same as a list comprehension, but in parentheses:

squares_list = [x * x for x in range(5)]      # a list, computed in full
squares_gen = (x * x for x in range(5))       # a generator, nothing computed yet
print(squares_list)                            # [0, 1, 4, 9, 16]
print(squares_gen)                             # <generator object ...>
print(sum(x * x for x in range(5)))            # 30

When a generator expression is the only argument of a function, you can drop the extra parentheses: sum(x * x for x in range(5)). This is the most common use: computing a sum, a maximum or any/all without building a list.

lines = ["1", "", "3"]
print(any(line == "" for line in lines))    # True
print(max(len(line) for line in lines))     # 1

Why you need them#

Memory. A list comprehension over a million items creates a million objects at once; a generator holds one. You can see the difference with sys.getsizeof: a list's size grows with its length, a generator's stays constant.

Early exit. If the answer is found at the third item, the rest are never computed:

def logged(items):
    for item in items:
        print("computing", item)
        yield item * 2

for value in logged([1, 2, 3]):
    if value >= 4:
        break
print("stopped")
  # computing 1 / computing 2 / stopped

Infinity. A generator may never end — that is fine as long as something else limits it:

def naturals():
    n = 1
    while True:
        yield n
        n += 1

from itertools import islice
print(list(islice(naturals(), 5)))   # [1, 2, 3, 4, 5]

yield from passes along every value of another generator or sequence without an explicit loop: yield from range(3) yields 0, 1, 2.

Pitfalls#

A generator is single-use. Once you have gone through it, it is empty:

gen = (x for x in [1, 2, 3])
print(list(gen))   # [1, 2, 3]
print(list(gen))   # []

This bites in code that first prints the result and then tries to count it. If you need to process the data again, save it to a list or create the generator anew.

len does not work. A generator has no length: len(gen) raises TypeError: object of type 'generator' has no len(). You can count items only by going through them: sum(1 for _ in gen) — and after that the generator is empty.

No indexing either. gen[0] is impossible; use next(gen) or itertools.islice.

Late binding. A generator expression reads outer variables at the moment it is iterated, not when it is created. If a variable changes in between, the result changes too.

A short exercise: write a generator read_numbers(path) that yields one number at a time from a file, and add them up with sum(...) without building a list. Reading line by line is covered in the article on reading files in Python, and how functions with parameters work is in the article on Python functions.

Step-by-step plan

  1. Your first yieldWrite a generator of three values and call next four times until you get StopIteration.
  2. Compare with a listBuild the same data with a list comprehension and a generator expression and print both objects.
  3. Count without a listFind the sum of squares of the first thousand numbers using sum with a generator expression.
  4. See the lazinessPut a print inside the generator and break out of the loop early — check that nothing extra was computed.
  5. Hit the single-use trapLoop over a generator twice in a row and explain why the second pass is empty.

Start learning this in your own space

The plan goes into your repository: tick off stages, keep notes — the change history shows how far you have come.

Start the plan

Check yourself

1.What does this print: g = (x for x in [1, 2, 3]); list(g); print(len(list(g)))?

2.What does sum(x * x for x in range(5)) return?

3.What does print(type((x for x in range(3))).__name__) print?

4.How many values does this generator yield before StopIteration: def g(): yield from range(4)?

Sources

Was this helpful?

More articles

Programming and IT How to learn Python from scratch Python is a good first programming language: code reads almost like text, and the standard library covers most everyday tasks. This plan takes you from installing the interpreter to your own scripts covered by tests in about four months, at roughly an hour a day. Programming and IT Python: slicing A slice cuts a piece out of a sequence by the rule "from which index, up to which, with what step". The notation is compact, but it has two places where almost everyone slips: the right bound is not included, and with a negative step the bounds swap places. Let us go through both. Programming and IT Python: lists A list is a mutable, ordered collection of values of any type. It is also the main source of surprises for beginners: assignment does not copy a list, sort returns nothing, and removing items inside a loop skips half of them. Let us go through it in order. Programming and IT Python: regular expressions A regular expression is a pattern that describes a set of strings. In Python you work with them through the `re` module from the standard library. You write the pattern as a string with an `r` prefix, search with `re.search` and pull the result out of a match object. Everything else is details of the pattern language. Programming and IT Python: classes A class describes what data an object is made of and what it can do. The `__init__` constructor fills in a new object, and `self` is a reference to the object itself. Below: a minimal working class, the difference between class and instance attributes, and ways to make the code shorter. Programming and IT Python: strings A string in Python is an immutable sequence of characters. Almost all of its quirks follow from that: methods do not change a string but return a new one, and fast text building goes through join, not through adding strings in a loop. Below is the working minimum with examples and output.

More solutions