Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does a yield inside a yield do?

Consider the following code:

def mygen():
     yield (yield 1)
a = mygen()
print(next(a))
print(next(a)) 

The output yields:

1
None

What does the interpreter do at the "outside" yield exactly?

like image 620
David Avatar asked Apr 30 '19 13:04

David


People also ask

What is the function of yield?

The YIELD Function[1] is categorized under Excel Financial functions. It will calculate the yield on a security that pays periodic interest. The function is generally used to calculate bond yield. As a financial analyst, we often calculate the yield on a bond to determine the income that would be generated in a year.

What does yield do in generators?

The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword. yield can only be called directly from the generator function that contains it.

Can a function have both yield and return?

Yes, it is still a generator. An empty return or return None can be used to end a generator function. It is equivalent to raising a StopIteration (see @NPE's answer for details). Note that a return with non-None arguments is a SyntaxError in Python versions prior to 3.3.

What does yield do in Pytest?

Since pytest-3.0, fixtures using the normal fixture decorator can use a yield statement to provide fixture values and execute teardown code, exactly like yield_fixture in previous versions.


3 Answers

a is a generator object. The first time you call next on it, the body is evaluated up to the first yield expression (that is, the first to be evaluated: the inner one). That yield produces the value 1 for next to return, then blocks until the next entry into the generator. That is produced by the second call to next, which does not send any value into the generator. As a result, the first (inner) yield evaluates to None. That value is used as the argument for the outer yield, which becomes the return value of the second call to next. If you were to call next a third time, you would get a StopIteration exception.

Compare the use of the send method (instead of next) to change the return value of the first yield expression.

>>> a = mygen()
>>> next(a)
1
>>> a.send(3)  # instead of next(a)
3
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

A more explicit way of writing the generator would have been

def mygen():
    x = yield 1
    yield x

a = mygen()
print(a.send(None))  # outputs 1, from yield 1
print(a.send(5))     # makes yield 1 == 5, then gets 5 back from yield x
print(a.send(3))     # Raises StopIteration, as there's nothing after yield x

Prior to Python 2.5, the yield statement provided one-way communication between a caller and a generator; a call to next would execute the generator up to the next yield statement, and the value provided by the yield keyword would serve as the return value of next. The generator would also suspend at the point of the yield statement, waiting for the next call to next to resume.

In Python 2.5, the yield statement was replaced* with the yield expression, and generators acquired a send method. send works very much like next, except it can take an argument. (For the rest of this, assume that next(a) is equivalent to a.send(None).) A generator starts execution after a call to send(None), at which point it executes up to the first yield, which returns a value as before. Now, however, the expression blocks until the next call to send, at which point the yield expression evaluates to the argument passed to send. A generator can now receive a value when it resumes.


* Not quite replaced; kojiro's answer goes into more detail about the subtle difference between a yield statement and yield expression.

like image 98
chepner Avatar answered Oct 22 '22 11:10

chepner


yield has two forms, expressions and statements. They're mostly the same, but I most often see them in the statement form, where the result would not be used.

def f():
    yield a thing

But in the expression form, yield has a value:

def f():
    y = yield a thing

In your question, you're using both forms:

def f():
    yield ( # statement
        yield 1 # expression
    )

When you iterate over the resulting generator, you get first the result of the inner yield expression

>>> x=f()
>>> next(x)
1

At this point, the inner expression has also produced a value that the outer statement can use

>>> next(x)
>>>  # None

and now you've exhausted the generator

>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

To understand more about statements vs expressions, there are good answers in other stackoverflow questions: What is the difference between an expression and a statement in Python?

like image 28
kojiro Avatar answered Oct 22 '22 12:10

kojiro


>>> def mygen():
...     yield (yield 1)
...
>>> a = mygen()
>>>
>>> a.send(None)
1
>>> a.send(5)
5
>>> a.send(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
>>>
>>>
>>> def mygen():
...     yield 1
...
>>> def mygen2():
...     yield (yield 1)
...
>>> def mygen3():
...     yield (yield (yield 1))
...
>>> a = mygen()
>>> a2 = mygen2()
>>> a3 = mygen3()
>>>
>>> a.send(None)
1
>>> a.send(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a2.send(None)
1
>>> a2.send(0)
0
>>> a2.send(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a3.send(None)
1
>>> a3.send(0)
0
>>> a3.send(1)
1
>>> a3.send(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

Every other yield simply waits for a value to be passed into, generator don't only give data but they also receive it.


>>> def mygen():
...     print('Wait for first input')
...     x = yield # this is what we get from send
...     print(x, 'is received')
...
>>> a = mygen()
>>> a.send(None)
Wait for first input
>>> a.send('bla')
bla is received
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

yield gives the next value when you continue if you get it, and if it is not used for giving the next value, it is being used for receiving the next

>>> def mygen():
...     print('Wait for first input')
...     x = yield # this is what we get from send
...     yield x*2 # this is what we give
...
>>> a = mygen()
>>> a.send(None)
Wait for first input
>>> a.send(5)
10
>>>
like image 4
Işık Kaplan Avatar answered Oct 22 '22 13:10

Işık Kaplan