def numberGenerator(number_range: int):
for i in range(number_range+1):
yield i
def loudNumberGenerator(number_range: int):
normal_number_generator = numberGenerator(number_range=number_range)
while True:
print('I am going to yield something!')
yield from normal_number_generator
zero_to_five = loudNumberGenerator(5)
for _ in range(6):
print(next(zero_to_five))
Expected:
I am going to yield something!
0
I am going to yield something!
1
I am going to yield something!
2
I am going to yield something!
3
I am going to yield something!
4
I am going to yield something!
5
Got:
I am going to yield something!
0
1
2
3
4
5
Why did the string I am going to yield something! printed only once?
What is yield from in simple words?
The values produced by iterating that iterable are passed directly to the caller of the current generatorâs methods. Any values passed in with send() and any exceptions passed in with throw() are passed to the underlying iterator if it has the appropriate methods.
Exactly what it sounds like: it yields the values from the other source, one at a time, until it runs out. Every time there is a request for the next element from this generator, if there is still something in the other source, it will use that. Otherwise, the logic proceeds.
The loop
while True:
print('I am going to yield something!')
yield from normal_number_generator
is effectively equivalent to:
while True:
print('I am going to yield something!')
for i in normal_number_generator:
yield i
Because yield from makes all of the values from normal_number_generator get yielded (and thus printed in the main loop), but then the while True: loop does not resume, because there are no more next calls on that generator - so itâs âstuckâ on the yield from line. If you try next(zero_to_ten) again after the code you showed, it will get in an infinite loop, because the while True: loop can continue, but yield from doesnât have any more elements to offer, so it tries forever looking for the next actual value to yield.
Why would it? It does its own iteration for âthat iterableâ, which has to finish before your while True: loop can proceed.
Close! Itâs that, but keep going until it stops, and also, if you get sent any values, pass those along too. Itâs fairly complicated to describe in full detail, but the short answer is: Itâs the generatorâs equivalent of calling another function. That is, it allows you to refactor a generator without changing its behaviour.
def by_yield():
x = yield 1
y = yield 2
z = yield 3
print(x, y, z)
def by_yield_from():
xyz = yield from by_yield()
print(f"{xyz=}")
g = by_yield_from()
next(g)
g.send('A')
g.send('B')
g.send('C')
Got:
A B C
xyz=None
Traceback (most recent call last):
File "test.py", line 22, in <module>
g.send('C')
StopIteration
Hmmm⌠Seems like xyz wonât be changed by g.send(). So I guess anything send()-ed to g will just go to the âinnerâ generator yield from directly, and hence xyz will always be None?
Wait, I was wrong, add a return statement in by_yield(),
def by_yield():
x = yield 1
y = yield 2
z = yield 3
print(x, y, z)
return 99
will get
A B C
xyz=99
According to the PEP:
Furthermore, when the iterator is another generator, the subgenerator is allowed to execute a return statement with a value, and that value becomes the value of the yield from expression.
Well, yes. Thatâs the point of yield from - itâs a syntax for delegating to a subgenetrator. The send() goes to the current yield, not to the yield from itself.
Incidentally, itâs the presence of features like send() that make yield from so valuable. Itâs more than just for thing in subgen: yield thing specifically BECAUSE it passes on any send/throw to the other. Thus it makes the perfect tool for this sort of delegation⌠or for some sort of, I dunno, async/await implementation done using generators And in fact, the PEP that introduces the async and await keywords specifically references yield from as it was a way to create coroutines without explicit support for asynchronous functions.