Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does nesting "yield from" statements (generator delegation) produce terminating `None` value?

Is it possible to nest yield from statements?

The simple form is obvious:

def try_yield1():
    x = range(3)
    yield from x

Which produces:

0
1
2

But what if I have nested generators?

def try_yield_nested():
   x = [range(3) for _ in range(4)]
    yield from ((yield from y) for y in x)

This produces:

0
1
2
None # why?
0
1
2
None # ...
0
1
2
None # ...

Why does it produce None if I used yield from (even though it is nested)?

I know I can do something like:

from itertools import chain

def try_yield_nested_alternative():
    x = [range(3) for _ in range(4)]
    yield from chain.from_iterable(x)

Which produces the same output leaving out the None (which is what I expect). I can also write a simple loop:

for x in [range(3) for _ in range(3)]:
    yield from x

But, I thought it would be more pythonic to use nested delegation (preferably even yield from x from y or yield from x for x in y, but that is not proper syntax). Why is it not working as I expect?

like image 856
Reut Sharabani Avatar asked Apr 13 '17 06:04

Reut Sharabani


People also ask

What takes place when a generator function calls yield?

When you call a function that contains a yield statement anywhere, you get a generator object, but no code runs. Then each time you extract an object from the generator, Python executes code in the function until it comes to a yield statement, then pauses and delivers the object.

What does the yield function do in Python?

yield in Python can be used like the return statement in a function. When done so, the function instead of returning the output, it returns a generator that can be iterated upon. You can then iterate through the generator to extract items.

Does yield exit function?

The keyword yield causes the function to hand back a generator object to its caller. yield will not cause the function to exit nor terminate the loop.

What is generator and yield in Python?

The yield keyword in python works like a return with the only difference is that instead of returning a value, it gives back a generator function to the caller. A generator is a special type of iterator that, once used, will not be available again. The values are not stored in memory and are only available when called.


1 Answers

yield is an expression, it'll evaluate whatever you send(value) it. Simply put, if you send it nothing, you'll get a None output because the value of yield would be None.

yield from itself is equal to None, and you're using two of them. At the second yield from you're iterating a list, which inputs the generator with the items of that list, but you're also using yield from to return that generator expression, which returns the list and also itself which is equivalent to None at each iteration, thus why after each item iteration of range you get a None.

Essentially this is what is happening:

  1. (yield from y) for y in x yields the values, [0, 1, 2]
  2. yield from yields the values from the previous, and also yield from from previous which is None.
  3. Evaluating to [0, 1, 2] and then adding a None due to yield from in (yield from y) for y in x.

Unfortunately, there is no way to get rid of the None output, due to the nature of the expression. You're better off using from_iterable.

Source explaining yield expression; https://docs.python.org/2.5/ref/yieldexpr.html

like image 108
L33T Avatar answered Nov 12 '22 15:11

L33T