A comprehension in round brackets: it describes the values, and computes each one only when it is asked for.
# generator expression (lazy)
gen = (x**2 for x in range(5))
# ans: <generator object>
list(gen)
# ans: [0, 1, 4, 9, 16]
Making gen computes nothing. list() pulls the values through one by one, squaring each as it passes.
# list - all in memory
lst = [x**2 for x in range(1000)]
# generator - on demand
gen = (x**2 for x in range(1000))
computed up front, held in memory
x = 0: only the current value exists
# iterate once
gen = (x for x in range(3))
for val in gen:
print(val)
# ans: 0, 1, 2
# ans: exhausted after iteration
for val in gen:
print(val)
# ans: (nothing)
# use in sum
sum(x**2 for x in range(10))
# ans: 285
# use in max
max(x for x in range(10) if x % 2 == 0)
# ans: 8
When the generator is the only argument, its own brackets can be dropped. No list is ever built.
# filter with generator
gen = (x for x in range(10) if x % 2 == 0)
list(gen)
# ans: [0, 2, 4, 6, 8]
Odd values are checked and skipped (red); only even ones come out.
Predict the answer, then press → to check.
# generator type?
gen = (x for x in range(3))
type(gen)
# ans: <class 'generator'>
# can iterate twice?
gen = (x for x in range(3))
list(gen)
# ans: [0, 1, 2]
list(gen)
# ans: [] (exhausted)
# memory efficiency?
import sys
lst = [x for x in range(1000)]
gen = (x for x in range(1000))
sys.getsizeof(lst)
# ans: ~9000 bytes
sys.getsizeof(gen)
# ans: ~200 bytes
# next() on generator?
gen = (x for x in range(3))
next(gen)
# ans: 0
next(gen)
# ans: 1
# generator with if-else?
gen = (x if x > 0 else 0 for x in [-1, 1, 2])
list(gen)
# ans: [0, 1, 2]
An if-else before for transforms every value; nothing is filtered out.
# chaining generators?
gen1 = (x for x in range(5))
gen2 = (x*2 for x in gen1)
list(gen2)
# ans: [0, 2, 4, 6, 8]
Each value is pulled from gen1 and doubled by gen2 on its way out.
# generator in sum?
sum(x for x in range(101))
# ans: 5050
# empty generator?
gen = (x for x in [])
list(gen)
# ans: []
# generator vs range?
range(10) # range object
(x for x in range(10)) # generator
# ans: both are lazy
But a range can be iterated again and indexed; a generator is used up after one pass.
# convert to list?
gen = (x**2 for x in range(3))
lst = list(gen)
# ans: [0, 1, 4]
# ans: gen is now exhausted
(expr for x in it if cond) makes a generator, not a tuple.
Values are computed when asked for, so memory stays tiny.
After it is exhausted, it gives nothing more.
sum, max, list and friends consume generators directly.