Reputation: 2871
There's a number of good questions about similar matters, e.g.
python generator "send" function purpose?
What does the "yield" keyword do?
Lets get back to a definition of "send":
Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value. When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value
But I feel I am missing something important. Here's my example with 3 send
calls, including the initial one with a None
value just to initialize a generator:
def multiplier():
while True:
m = yield # Line #3
print('m = ' + str(m)) # Line #4
yield str(m * 2) # Line #5
yield str(m * 3) # Line #6
#------------------------
it = multiplier()
print('it.send(None): ')
print(str(it.send(None)))
print('--------------')
print('it.send(10): ')
print(it.send(10))
print('--------------')
print('it.send(100): ')
print(it.send(100))
print('--------------')
And here's an output:
it.send(None):
None
--------------
it.send(10):
m = 10
20
--------------
it.send(100):
30
--------------
Questions:
What happens exactly when I use it.send(10)
in a line #5. If we
follow the definition, the generator execution resumes. Generator
accepts 10
as input value and uses it in a current yield
expression
. It is yield str(m * 2)
in my example, but then how m
is set to 10
. When did that happen. Is that because of the
reference between m
and yield
in a line #3?
What happens in a line #6 it.send(10)
and why output is still 30
?
Does it mean that the reference in my previous question only worked
once?
Note:
If I've changed my example and added a line m = yield
between lines #5 and #6 and then use next(it)
after print(it.send(10))
- in that case the output starts to make sense: 20 and 300
Upvotes: 3
Views: 1026
Reputation: 104752
Your generator function has three yield
expressions, but you're throwing away the value from two of them (lines 5 and 6). If you did something with the values there, you'd see the 100
being used in the function. If you kept running your example, the fifth time you called send
would cause the generator to update m
to a new value.
Lets walk through the code that does the send
calls in your example, and see what the generator is doing at the same time:
it = multiplier()
At this point the generator object has been created and saved to it
. The generator code has not started running yet, it's paused at the start of the function's code.
print(str(it.send(None)))
This starts running the generator function's code. The value sent must be None
or you'll get an error. The function never sees that value. It's more common to use next
to start up a generator, since next(it)
is equivalent to it.send(None)
.
The generator function runs until line 3, where the first yield
appears. Since you're not yielding any particular value, the return value from send
is None
(which gets printed).
print(it.send(10))
This value gets sent to the generator and becomes the value of the yield
expression on line 3. So 10
gets stored as m
, and the code prints it out on line 4. The generator function keeps running to line 5, where it reaches the next yield
expression. Since it's yielding str(m * 2)
, the calling code gets "20"
and prints that.
print(it.send(100))
The 100 value gets sent into the generator as the value of the yield
on line 4. That value is ignored, since you're not using the yield
as an expression but as a statement. Just like putting 100
on a line by itself, this is perfectly legal, but maybe not very useful. The code goes on to line 5 where it yields str(m * 3)
, or "30"
, which gets printed by the calling code.
That's where your driving code stops, but the generator is still alive, and you could send more values to it (and get more values back). The next value you send
to the generator would also be ignored, just like the 100
was, but the value after that would end up as a new m
value when the while
loop in the generator returned to the top and the line 3 yield
was reached.
I suspect that some of your confusion with send
in this code has to do with the fact that you're using yield
both as an expression and as a statement. Probably you don't want to be doing both. Usually you'll either care about all the values being sent into the generator, or you don't care about any of them. If you want to yield several values together (like n*2
and n*3
), you could yield a tuple rather than a single item.
Here's a modified version of your code that I think might be easier for you to play with and understand:
def multiplier():
print("top of generator")
m = yield # nothing to yield the first time, just a value we get
print("before loop, m =", m)
while True:
print("top of loop, m =", m)
m = yield m * 2, m * 3 # we always care about the value we're sent
print("bottom of loop, m =", m)
print("calling generator")
it = multiplier()
print("calling next")
next(it) # this is equivalent to it.send(None)
print("sending 10")
print(it.send(10))
print("sending 20")
print(it.send(20))
print("sending 100")
print(it.send(100))
Upvotes: 2