blueFast
blueFast

Reputation: 44431

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

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?

Upvotes: 7

Views: 4021

Answers (3)

VPfB
VPfB

Reputation: 17332

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.

Upvotes: 1

Dunes
Dunes

Reputation: 40778

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.

Upvotes: 14

hiro protagonist
hiro protagonist

Reputation: 46901

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

Upvotes: 2

Related Questions