7

I haven't been able to find any examples of return values from the yield from expression. I have tried this simple code, without success:

def return4():
    return 4


def yield_from():
    res = yield from range(4)
    res = yield from return4()


def test_yield_from():
    for x in yield_from():
        print(x)


test_yield_from()

Which produces:

» python test.py 
0
1
2
3
Traceback (most recent call last):
  File "test.py", line 52, in <module>
    test_yield_from()
  File "test.py", line 48, in test_yield_from
    for x in yield_from():
  File "test.py", line 44, in yield_from
    res = yield from return4()
TypeError: 'int' object is not iterable

But I was expecting:

» python test.py 
0
1
2
3
4

Because, as stated in 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.

Obviously, I am not getting this explanation. How does a return in a "subgenerator" work with regards to yield from?

blueFast
  • 41,341
  • 63
  • 198
  • 344
  • 2
    I don't know. I am trying it out because I haven't found any examples. That's why I am asking here, obviously. – blueFast Oct 09 '15 at 10:31

3 Answers3

14

Generators can return a value when they are exhausted:

def my_gen():
    yield 0
    return "done"

g = my_gen()
next(g)
next(g) # raises StopIteration: "done"

The returned value in a yield from statement will be this value. eg.

def yield_from():
    res = yield from my_gen()
    assert res == "done"

By default this value is None. That is res = yield from range(4) will set res as None.

Dunes
  • 37,291
  • 7
  • 81
  • 97
  • ok, that seems more clear. What is the use case for this? Would I use this to refactor a normal function into a subgenerator, for example? – blueFast Oct 09 '15 at 11:18
  • Subgenerators and returning from a generator are used heavily in python's coroutine library. -- https://docs.python.org/3/library/asyncio-task.html . Here generators are used to create cooperative multithreading, rather to yield values. – Dunes Oct 09 '15 at 11:25
2

yield from generator is short for

for i in generator:
    yield i

well it's a bit more commplicated than that: https://www.python.org/dev/peps/pep-0380/#formal-semantics .

this will not work well if generator = 4. (your return4() is not a generator. it's a function.)

in order to get what you wand you would just do this:

def yield_from():
    yield from range(4)
    yield 4
hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • Sure. So, what do return statements in a subgenerator do? What is the meaning of: "the subgenerator is allowed to execute a return statement with a value, and that value becomes the value of the yield from expression" – blueFast Oct 09 '15 at 10:30
  • you call your function: `return4()`. at the point where `yield from` is executed this has already evalutated to `4`. – hiro protagonist Oct 09 '15 at 10:33
  • 1
    @jeckyll2hide if you had `def range4(): return range(4)` you could then use `yield from range4()` even though `range4` is not a generator. – Holloway Oct 09 '15 at 10:40
  • @Trengot ...that probably answers the question better than my attempt. – hiro protagonist Oct 09 '15 at 10:55
  • @Trengot: no, this is not what I am (and the PEP) talking about. I am talking about returning **values** from the subgenerator, not returning a generator. – blueFast Oct 09 '15 at 11:21
1

I'm posting a working example for your tests.

return4 function is now a generator. To achieve that, a yield must be present anywhere in the function (there is a new related feature in Python 3.5, but that's not important now).

As you quoted already:

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

Summary: you will get a value. You could print it, for example:

def yield_from():
    # ...
    val = yield from return4()
    print("value:", val)  # prints value: 4

But you want to yield it, not print. Here is the complete code:

def return4():
    if False:
        yield None
    return 4

def yield_from():
    yield from range(4)
    yield (yield from return4())

def test_yield_from():
    for x in yield_from():
        print(x)

test_yield_from()
# prints numbers 0 to 4

You are probably asking yourself, what is it good for. There is almost no advantage when you are only receivng values from a generator. But yield from is a great feature when you are sending values to a generator. Try to find a good explanation of python coroutines. It's amazing.

VPfB
  • 14,927
  • 6
  • 41
  • 75