Functions that pause: yield hands out one value at a time and picks up where it left off.
Calling a generator function only creates a generator object, with its frame parked and its body not yet started.
next() resumesThe frame goes back on the stack and runs until the next yield.
yield pausesThe value goes to the caller and the frame is parked again, local variables and all. Watch the Paused generators shelf.
# generator with yield
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
for num in count_up_to(3):
print(num)
# ans: 1, 2, 3
# yield pauses function
def simple_gen():
yield 1
yield 2
yield 3
gen = simple_gen()
next(gen)
# ans: 1
next(gen)
# ans: 2
return would end the call for good; yield keeps the frame for the next next().
# generator is an iterator
def my_gen():
yield 1
yield 2
gen = my_gen()
next(gen)
# ans: 1
# can only iterate once
list(gen)
# ans: [2]
list(gen)
# ans: []
# infinite sequence
def infinite_counter():
count = 0
while True:
yield count
count += 1
# use with break or take first n
counter = infinite_counter()
for i, num in enumerate(counter):
if i >= 3:
break
print(num)
# ans: 0, 1, 2
An endless loop is fine in a generator: it only runs when asked for the next value.
# filter in generator
def even_numbers(n):
for i in range(n):
if i % 2 == 0:
yield i
list(even_numbers(10))
# ans: [0, 2, 4, 6, 8]
Only values that pass the test are yielded; the others are skipped inside the loop.
# delegate to another generator
def generator1():
yield 1
yield 2
def generator2():
yield from generator1()
yield 3
list(generator2())
# ans: [1, 2, 3]
yield from passes on every value of the inner generator, then carries on.
Predict the answer, then press → to check.
# what is returned?
def gen():
yield 1
result = gen()
type(result)
# ans: <class 'generator'>
# yield in loop?
def gen():
for i in range(3):
yield i
list(gen())
# ans: [0, 1, 2]
# generator exhaustion?
def gen():
yield 1
yield 2
g = gen()
next(g)
# ans: 1
next(g)
# ans: 2
next(g)
# ans: StopIteration
# multiple yields?
def gen():
yield 1
yield 2
return 3
list(gen())
# ans: [1, 2] (the return value is ignored)
# generator vs list?
def gen():
yield 1
list(gen())
# ans: [1]
gen()
# ans: <generator object>
# fibonacci generator?
def fib(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
list(fib(5))
# ans: [0, 1, 1, 2, 3]
# yield from list?
def gen():
yield from [1, 2, 3]
list(gen())
# ans: [1, 2, 3]
# generator with return?
def gen():
yield 1
return 2
g = gen()
next(g)
# ans: 1
next(g)
# ans: StopIteration (with value 2)
# send to generator?
def gen():
value = yield
print(value)
g = gen()
next(g) # prime
g.send("Hello")
# ans: prints "Hello"
send() resumes the generator and makes the paused yield evaluate to the value sent.
# generator memory?
def gen():
for i in range(1000000):
yield i
# ans: memory efficient (lazy)
lst = [i for i in range(1000000)]
# ans: all in memory at once
Calling a generator function runs nothing until the first next().
Each yield hands out a value and keeps the local variables for later.
Once exhausted, a generator raises StopIteration and gives nothing more.
Values are made one at a time instead of stored all at once.