PythonGenerator Expressions

Generator Expressions

A generator expression is a compact way to create a generator without writing a full def function with a yield statement. It looks almost exactly like a list comprehension, except it uses parentheses instead of square brackets — and that single difference in punctuation changes its behavior completely, from eager to lazy.

List Comprehension vs Generator Expression

Compare the two side by side. Both describe "the square of each number from 0 to 9," but they produce very different objects.

Python
squares_list = [x**2 for x in range(10)]
print(squares_list)
print(type(squares_list))
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# <class 'list'>

Python
squares_gen = (x**2 for x in range(10))
print(squares_gen)
print(type(squares_gen))
# <generator object <genexpr> at 0x...>
# <class 'generator'>

for value in squares_gen:
    print(value)
# 0
# 1
# 4
# ...
# 81

The list comprehension builds the entire list of ten squared values immediately and stores all of them in memory before the line even finishes executing. The generator expression instead builds a small generator object that knows how to produce each square one at a time — nothing is actually computed until you iterate over it.

Memory And Behavior Comparison

Aspect

List Comprehension

Generator Expression

Memory usage

All items are computed and stored in memory at once

Only one item at a time is held in memory as it is produced

Evaluation timing

Eager — the whole list is built the moment the line runs

Lazy — values are computed on demand, as they are requested

Reusability

Can be iterated over any number of times

Single-use — once fully iterated it is exhausted and yields nothing on a second pass

Typical use case

Small-to-medium data you need to index, re-iterate, or measure with len()

Large or infinite sequences you only need to scan once, or feed straight into another function

Passing A Generator Expression Directly Into A Function

When a generator expression is the only argument being passed to a function call, you can drop the extra pair of parentheses — the ones that belong to the function call double up as the generator expression's own parentheses.

Python
total = sum(x**2 for x in range(1_000_000))
print(total)

Python
data = [5, 12, 8, 150, 3]

has_large_value = any(x > 100 for x in data)
print(has_large_value)  # True

In both examples, sum() and any() pull values from the generator expression one at a time and stop as soon as they have their answer — any() in particular can short-circuit and stop iterating the moment it finds a value greater than 100, without ever computing the rest. Writing sum((x**2 for x in range(1_000_000))) with the extra parentheses would also work, but it is unnecessary noise when the generator expression is the sole argument.

Choosing Between A List And A Generator
  • Reach for a list (or list comprehension) when you need to index into the results, iterate over them more than once, check its length with len(), or pass it somewhere that specifically expects a list.

  • Reach for a generator expression when you only need to iterate once — especially over a large dataset, a file, or a conceptually infinite sequence — and building the full collection in memory would be wasteful or impossible.

Warning
A generator expression is exhausted after one complete iteration. Trying to iterate it a second time produces no values at all — it does not raise an error, it simply yields nothing, which can be a subtle source of bugs.

Python
gen = (x for x in range(3))

print(list(gen))  # [0, 1, 2] - first pass consumes the generator
print(list(gen))  # [] - second pass: already exhausted, nothing left
Tip
If you need to run through the same values more than once, either recreate the generator expression each time (call it again, the way you would call a generator function again), or use a list comprehension instead so the values stick around.