1
def func():
    output = 0
    while True:
        new = yield output
        output = new


genr = func()
print(next(genr))
print(next(genr))
print(next(genr))

output:

0
None
None

What i thought is:

  1. genr=func() return a generator, but does not actually run it.
  2. First print(next(genr)) run from the begining of func to yield output, but not yet assign back to new,so output 0 make sense.
  3. Second print(next(genr)) start from assigning output back to new,and next line output = new make both output and new to 0, next execute yield output should return 0, but why it return None actually?
3
  • 3
    Don't assign new to yield output. The result of that operation is None Commented Sep 5, 2016 at 2:43
  • You mean yield output won't be assign back to new? do you have any reference information, thank you.
    – good5dog5
    Commented Sep 5, 2016 at 2:50
  • Have a search for coroutines Commented Sep 5, 2016 at 3:03

2 Answers 2

9

A yield statement is used like return to return a value but it doesn't destroy the stack frame (the part of a function that knows the current line, local variables, and pending try-statements). This allows the function to be resumed after the yield.

When you call a function containing yield, it returns a "generator" that allows you to run code up to a yield and then to resume it from where it left off.

>>> def squares(n):
        for i in range(n):
            yield i ** 2

>>> g = squares(5)             # create the generator
>>> g
<generator object squares at 0x106beef10>
>>> next(g)                    # run until the first yield
0
>>> next(g)                    # resume after the yield
1
>>> next(g)                    # resume after the yield
4
>>> next(g)                    # resume after the yield
9
>>> next(g)                    # resume after the yield
16
>>> next(g)                    # looping is terminated with a StopIteration
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    next(g)                    # looping is terminated with a StopIteration
StopIteration

Interestingly, a generator can accept values using the send() method. To prime the pump for such a generator the first call should be next().

>>> def capitalize():
        word = 'start'
        while word != 'quit':
            word = yield word.capitalize()

>>> g = capitalize()
>>> next(g)                      # run to the first yield
'Start'
>>> g.send('three')              # send in a value to be assigned to word
'Three'
>>> g.send('blind')              # send in a value to be assigned to word
'Blind'
>>> g.send('mice')               # send in a value to be assigned to word
'Mice'
>>> g.send('quit')               # send in a control value
Traceback (most recent call last):
  File "<pyshell#28>", line 1, in <module>
    g.send('quit')               # send in a control value
StopIteration

What you've figured-out in your example is that next(g) is really the same as g.send(None).

Here's what the docs have to say:

The value of the yield expression after resuming depends on the method which resumed the execution. If __next__() is used (typically via either a for or the next() builtin) then the result is None. Otherwise, if send() is used, then the result will be the value passed in to that method

Here's a session that makes all of that visible:

>>> def show_expression():
        for i in range(5):
            word = yield 10
            print('The word is %r' % word)

>>> g = show_expression()
>>> next(g)
10
>>> g.send('blue')
The word is 'blue'
10
>>> g.send('no')
The word is 'no'
10
>>> g.send('yellow')
The word is 'yellow'
10
>>> next(g)
The word is None
10
>>> g.send('done')
The word is 'done'
Traceback (most recent call last):
  File "<pyshell#44>", line 1, in <module>
    g.send('done')
StopIteration

Hope that explains all the mysteries from first principles :-)

3
  • So, in my problem code, the new = yield output statement does not execute completely? just yield output without assigning back the result to new?
    – good5dog5
    Commented Sep 5, 2016 at 7:40
  • @user2706322 Yes, though I would say it a bit differently. The yield output returns the output value and suspends the execution. Later, when you write g.next() which is equivalent to g.send(None), the execution completes, with the passed-in None value becoming result of the expression and being assigned to new. In other words, it executes completely but is broken into two steps, one before the next and one after. Commented Sep 5, 2016 at 7:50
  • I still have 1 quesion, why first g.next() let yield output produce 0 and second let produce None? In the second g.next(), the output should still be 0 doesn't it?
    – good5dog5
    Commented Sep 5, 2016 at 9:35
4

The result of a yield expression is the value sent in by the generator.send() function, and next(gen) is equivalent to gen.send(None). So new receives the value None each time you call next().

If you do this instead:

gen = func()
print(next(gen))     # gets the first value of 'output'
print(next(gen))     # send in None, get None back
print(gen.send(10))  # send in 10, get 10 back
print(gen.send(20))  # send in 20, get 20 back

you'll get this output:

0
None
10
20
1
  • So, the new = yield output statement does not execute completely? just yield output without assigning back the result to new?
    – good5dog5
    Commented Sep 5, 2016 at 7:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.