I understand yield
. But what does a generator's send
function do? The documentation says:
generator.send(value)
Resumes the execution and “sends” a value into the generator function. The
value
argument becomes the result of the currentyield
expression. Thesend()
method returns the next value yielded by the generator, or raisesStopIteration
if the generator exits without yielding another value.
What does that mean? I thought value
was the input to the generator function? The phrase "The send()
method returns the next value yielded by the generator" seems to be also the exact purpose of yield
, which also returns the next value yielded by the generator.
Is there an example of a generator utilizing send
that accomplishes something yield
cannot?
It's used to send values into a generator that just yielded. Here is an artificial (non-useful) explanatory example:
>>> def double_inputs():
... while True:
... x = yield
... yield x * 2
...
>>> gen = double_inputs()
>>> next(gen) # run up to the first yield
>>> gen.send(10) # goes into 'x' variable
20
>>> next(gen) # run up to the next yield
>>> gen.send(6) # goes into 'x' again
12
>>> next(gen) # run up to the next yield
>>> gen.send(94.3) # goes into 'x' again
188.5999999999999
You can't do this just with yield
.
As to why it's useful, one of the best use cases I've seen is Twisted's @defer.inlineCallbacks
. Essentially it allows you to write a function like this:
@defer.inlineCallbacks
def doStuff():
result = yield takesTwoSeconds()
nextResult = yield takesTenSeconds(result * 10)
defer.returnValue(nextResult / 10)
What happens is that takesTwoSeconds()
returns a Deferred
, which is a value promising a value will be computed later. Twisted can run the computation in another thread. When the computation is done, it passes it into the deferred, and the value then gets sent back to the doStuff()
function. Thus the doStuff()
can end up looking more or less like a normal procedural function, except it can be doing all sorts of computations & callbacks etc. The alternative before this functionality would be to do something like:
def doStuff():
returnDeferred = defer.Deferred()
def gotNextResult(nextResult):
returnDeferred.callback(nextResult / 10)
def gotResult(result):
takesTenSeconds(result * 10).addCallback(gotNextResult)
takesTwoSeconds().addCallback(gotResult)
return returnDeferred
It's a lot more convoluted and unwieldy.