Whats the purpose of send function on Python generators?

Whats the purpose of send function on Python generators?

Its 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 cant do this just with yield.

As to why its useful, one of the best use cases Ive seen is Twisteds @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

Its a lot more convoluted and unwieldy.

This function is to write coroutines

def coroutine():
    for i in range(1, 10):
        print(From generator {}.format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print(From user {}.format(c.send(1)))
except StopIteration: pass

prints

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

See how the control is being passed back and forth? Those are coroutines. They can be used for all kinds of cool things like asynch IO and similar.

Think of it like this, with a generator and no send, its a one way street

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

But with send, it becomes a two way street

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

Which opens up the door to the user customizing the generators behavior on the fly and the generator responding to the user.

Whats the purpose of send function on Python generators?

This may help someone. Here is a generator that is unaffected by send function. It takes in the number parameter on instantiation and is unaffected by send:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

Now here is how you would do the same type of function using send, so on each iteration you can change the value of number:

def double_number(number):
    while True:
        number *= 2
        number = yield number

Here is what that looks like, as you can see sending a new value for number changes the outcome:

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

You can also put this in a for loop as such:

for x in range(10):
    n = c.send(n)
    print n

For more help check out this great tutorial.

Leave a Reply

Your email address will not be published. Required fields are marked *