What does a return do when using a "yield from" expression?
Asked Answered
V

3

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?

Vacuva answered 9/10, 2015 at 10:26 Comment(1)
I don't know. I am trying it out because I haven't found any examples. That's why I am asking here, obviously.Vacuva
E
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.

Engrossment answered 9/10, 2015 at 10:42 Comment(2)
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?Vacuva
Subgenerators and returning from a generator are used heavily in python's coroutine library. -- docs.python.org/3/library/asyncio-task.html . Here generators are used to create cooperative multithreading, rather to yield values.Engrossment
F
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
Fatidic answered 9/10, 2015 at 10:28 Comment(5)
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"Vacuva
you call your function: return4(). at the point where yield from is executed this has already evalutated to 4.Fatidic
@jeckyll2hide if you had def range4(): return range(4) you could then use yield from range4() even though range4 is not a generator.Ciliolate
@Trengot ...that probably answers the question better than my attempt.Fatidic
@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.Vacuva
E
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.

Euchologion answered 9/10, 2015 at 15:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.