Codectionary / Developer documentation / Python

Generator expressions

Compute a sequence lazily with comprehension syntax, consuming values only when a caller requests them.

Understand the contract

A generator expression looks like a list comprehension enclosed in parentheses, but returns a generator iterator instead of an eagerly built list. It is well suited to aggregations such as sum or any that consume values without needing to retain every intermediate result.

The outermost iterable expression is evaluated when the generator expression is created. The element expression and later iteration work occur as the generator advances. Laziness changes when work and exceptions happen; it does not make an expensive calculation disappear.

Follow the runtime behavior

Generators are one-pass iterators. After a consumer exhausts one, another consumer gets no remaining values. Materialize a list when repeated access, indexing, or a stable snapshot is required.

A generator can reduce intermediate storage but may still refer to a large source collection. Avoid mutating inputs or rebinding values used by a deferred expression unless that behavior is intentional and well understood.

Apply it and practise

Use generator expressions for straightforward transformations and filters passed directly to an aggregator. Switch to a generator function when multiple steps or error handling would make the expression difficult to read.

Exercise: compute the sum of squares of even numbers in range(8) using a generator expression. Then assign that generator to a variable and call sum twice; predict and explain both results.

Syntax

(expression for item in iterable if condition)

Examples

Aggregate without an intermediate list

Use this pattern for totals over a large iterable when individual transformed values are not needed later.

total = sum(value * value for value in range(6) if value % 2 == 0)
print(total)
  1. The filter accepts zero, two, and four. Their squares flow directly into sum, producing twenty without first storing a separate list of squares.
  2. Use this pattern for totals over a large iterable when individual transformed values are not needed later.
  3. The source still has its own memory cost, and computation still occurs for every inspected item.
  4. Expected output: 20

Observe deferred work

Use this trace to understand when a lazy pipeline performs work or raises errors.

def square(value):
    print(f"compute {value}")
    return value * value

values = (square(value) for value in [2, 3])
print("created")
print(next(values))
print(list(values))
  1. Creating values does not call square. next performs only the first calculation, and list then computes and collects the remaining value.
  2. Use this trace to understand when a lazy pipeline performs work or raises errors.
  3. Side effects inside transformations make consumption order observable; prefer pure calculations in production pipelines.
  4. Expected output: created compute 2 4 compute 3 [9]

Recognize one-pass consumption

Create a new generator for each independent traversal or store a list when reuse is essential.

values = (number + 1 for number in range(3))
print(list(values))
print(list(values))
fresh = (number + 1 for number in range(3))
print(sum(fresh))
  1. The first list call exhausts values. The second sees nothing, while a new generator expression creates fresh traversal state for sum.
  2. Create a new generator for each independent traversal or store a list when reuse is essential.
  3. Reusing an exhausted generator does not raise an error in list or sum; it can silently produce an empty aggregate.
  4. Expected output: [1, 2, 3] [] 6

Best practices

  • A generator can reduce intermediate storage but may still refer to a large source collection. Avoid mutating inputs or rebinding values used by a deferred expression unless that behavior is intentional and well understood.
  • Use generator expressions for straightforward transformations and filters passed directly to an aggregator. Switch to a generator function when multiple steps or error handling would make the expression difficult to read.

Runtime & versions

Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Python language reference

Specifications & further reading