Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python generator "send" function purpose?

Tags:

python

People also ask

What does send () do in Python?

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.

Why do we use generator function in Python?

Python Generator functions allow you to declare a function that behaves likes an iterator, allowing programmers to make an iterator in a fast, easy, and clean way. An iterator is an object that can be iterated or looped upon.

What is the purpose of a yield statement?

The yield statement returns a generator object to the one who calls the function which contains yield, instead of simply returning a value.

What is difference between generator and function in Python?

A normal function will return a sequence of items, but before giving the result, it creates a sequence in memory and then gives us the result, whereas the generator function produces one output at a time.


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.


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, it's 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.


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.


The send() method controls what the value to the left of the yield expression will be.

To understand how yield differs and what value it holds, lets first quickly refresh on the order python code is evaluated.

Section 6.15 Evaluation order

Python evaluates expressions from left to right. Notice that while evaluating an assignment, the right-hand side is evaluated before the left-hand side.

So an expression a = b the right hand side is evaluated first.

As the following demonstrates that a[p('left')] = p('right') the right hand side is evaluated first.

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

What does yield do?, yield, suspends execution of the function and returns to the caller, and resumes execution at the same place it left off prior to suspending.

Where exactly is execution suspended? You might have guessed it already... the execution is suspended between the right and left side of the yield expression. So new_val = yield old_val the execution is halted at the = sign, and the value on the right (which is before suspending, and is also the value returned to the caller) may be something different then the value on the left (which is the value being assigned after resuming execution).

yield yields 2 values, one to the right and another to the left.

How do you control the value to the left hand side of the yield expression? via the .send() method.

6.2.9. Yield expressions

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.