"Double" iterator and a generator function - python

I want to get "next asset" with an iterator-like object, but (instead of __next__() method) there are two algorithms loading next asset (next1 and next2 below), which can be implemented as a "quasi-iterator" like:
class AssetLoader(object):
def __init___(self):
pass
def next1(self):
# ...
def next2(self):
# ...
To be clear, what is the next retrieved object may depends on the "history" of calling next1 and next2, like:
next1(); next1(); next2(); next1(); next2()
My question: May this (two kinds of "next" step in an iterator) be implemented as a generator function?
I guess this can be done with a global variable to which the function refers. But can it be done without using global variables, but with some local variable?
If it is hard or impossible with current Python, can we discuss how to add new semantics to Python to make it possible?

Here's a simple example of using send to switch a generator between two different iteration modes: it either increments its current value or multiplies it. The same principle can be applied to your graph traversal task.
The send method allows you to send an object into the generator. Annoyingly, the result of send is the current value that you would have obtained by calling next; it would be nice if you could send without having the generator yield a value, but that's just something we have to live with.
def add_or_mul(current, step, scale, mode='add'):
''' A generator that either adds step to the current value,
or multiplies it by scale
'''
while True:
newmode = yield current
if newmode is not None:
if newmode not in ('add', 'mul'):
raise ValueError('Bad mode: ' + newmode)
mode = newmode
if mode == 'add':
current += step
else:
current *= scale
# Test
gen = add_or_mul(1, 1, 2)
for i in range(5):
print(next(gen))
print(gen.send('mul'))
for i in range(4):
print(next(gen))
print(gen.send('add'))
for i in range(4):
print(next(gen))
output
1
2
3
4
5
10
20
40
80
160
161
162
163
164
165
If you have trouble applying this technique to your graph traversal task please ask a fresh question (possibly linking to this one) that includes some relevant graph code, so that answerers don't have to write that stuff from scratch in order to test and demonstrate their code.

You can try this:
class AssetLoader(object):
def __init___(self):
self.current_next = self.next1
def next1(self):
if condition:
self.current_next = self.next2
elif conition:
return x
else:
raise StopIteration
def next2(self):
if condition:
self.current_next = self.next1
elif conition:
return y
else:
raise StopIteration
def __next__(self):
return self.current_next()
def __iter__(self):
return self

Related

In a generator, equality to yield [duplicate]

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 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.
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.
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.
Some use cases for using generator and send()
Generators with send() allow:
remembering internal state of the execution
what step we are at
what is current status of our data
returning sequence of values
receiving sequence of inputs
Here are some use cases:
Watched attempt to follow a recipe
Let us have a recipe, which expects predefined set of inputs in some order.
We may:
create a watched_attempt instance from the recipe
let it get some inputs
with each input return information about what is currently in the pot
with each input check, that the input is the expected one (and fail if it is not)
def recipe():
pot = []
action = yield pot
assert action == ("add", "water")
pot.append(action[1])
action = yield pot
assert action == ("add", "salt")
pot.append(action[1])
action = yield pot
assert action == ("boil", "water")
action = yield pot
assert action == ("add", "pasta")
pot.append(action[1])
action = yield pot
assert action == ("decant", "water")
pot.remove("water")
action = yield pot
assert action == ("serve")
pot = []
yield pot
To use it, first create the watched_attempt instance:
>>> watched_attempt = recipe()
>>> watched_attempt.next()
[]
The call to .next() is necessary to start execution of the generator.
Returned value shows, our pot is currently empty.
Now do few actions following what the recipe expects:
>>> watched_attempt.send(("add", "water"))
['water']
>>> watched_attempt.send(("add", "salt"))
['water', 'salt']
>>> watched_attempt.send(("boil", "water"))
['water', 'salt']
>>> watched_attempt.send(("add", "pasta"))
['water', 'salt', 'pasta']
>>> watched_attempt.send(("decant", "water"))
['salt', 'pasta']
>>> watched_attempt.send(("serve"))
[]
As we see, the pot is finally empty.
In case, one would not follow the recipe, it would fail (what could be desired outcome of watched
attempt to cook something - just learning we did not pay enough attention when given instructions.
>>> watched_attempt = running.recipe()
>>> watched_attempt.next()
[]
>>> watched_attempt.send(("add", "water"))
['water']
>>> watched_attempt.send(("add", "pasta"))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))
/home/javl/sandbox/stack/send/running.py in recipe()
29
30 action = yield pot
---> 31 assert action == ("add", "salt")
32 pot.append(action[1])
33
AssertionError:
Notice, that:
there is linear sequence of expected steps
the steps may differ (some are removing, some are adding to the pot)
we manage to do all that by a functions/generator - no need to use complex class or similar
strutures.
Running totals
We may use the generator to keep track of running total of values sent to it.
Any time we add a number, count of inputs and total sum is returned (valid for
the moment previous input was send into it).
from collections import namedtuple
RunningTotal = namedtuple("RunningTotal", ["n", "total"])
def runningtotals(n=0, total=0):
while True:
delta = yield RunningTotal(n, total)
if delta:
n += 1
total += delta
if __name__ == "__main__":
nums = [9, 8, None, 3, 4, 2, 1]
bookeeper = runningtotals()
print bookeeper.next()
for num in nums:
print num, bookeeper.send(num)
The output would look like:
RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)
The send method implements coroutines.
If you haven't encountered Coroutines they are tricky to wrap your head around because they change the way a program flows. You can read a good tutorial for more details.
The word "yield" has two meanings: to produce something (e.g., to yield corn), and to halt to let someone/thing else continue (e.g., cars yielding to pedestrians). Both definitions apply to Python's yield keyword; what makes generator functions special is that unlike in regular functions, values can be "returned" to the caller while merely pausing, not terminating, a generator function.
It is easiest to imagine a generator as one end of a bidirectional pipe with a "left" end and a "right" end; this pipe is the medium over which values are sent between the generator itself and the generator function's body. Each end of the pipe has two operations: push, which sends a value and blocks until the other end of the pipe pulls the value, and returns nothing; and pull, which blocks until the other end of the pipe pushes a value, and returns the pushed value. At runtime, execution bounces back and forth between the contexts on either side of the pipe -- each side runs until it sends a value to the other side, at which point it halts, lets the other side run, and waits for a value in return, at which point the other side halts and it resumes. In other words, each end of the pipe runs from the moment it receives a value to the moment it sends a value.
The pipe is functionally symmetric, but -- by convention I'm defining in this answer -- the left end is only available inside the generator function's body and is accessible via the yield keyword, while the right end is the generator and is accessible via the generator's send function. As singular interfaces to their respective ends of the pipe, yield and send do double duty: they each both push and pull values to/from their ends of the pipe, yield pushing rightward and pulling leftward while send does the opposite. This double duty is the crux of the confusion surrounding the semantics of statements like x = yield y. Breaking yield and send down into two explicit push/pull steps will make their semantics much more clear:
Suppose g is the generator. g.send pushes a value leftward through the right end of the pipe.
Execution within the context of g pauses, allowing the generator function's body to run.
The value pushed by g.send is pulled leftward by yield and received on the left end of the pipe. In x = yield y, x is assigned to the pulled value.
Execution continues within the generator function's body until the next line containing yield is reached.
yield pushes a value rightward through the left end of the pipe, back up to g.send. In x = yield y, y is pushed rightward through the pipe.
Execution within the generator function's body pauses, allowing the outer scope to continue where it left off.
g.send resumes and pulls the value and returns it to the user.
When g.send is next called, go back to Step 1.
While cyclical, this procedure does have a beginning: when g.send(None) -- which is what next(g) is short for -- is first called (it is illegal to pass something other than None to the first send call). And it may have an end: when there are no more yield statements to be reached in the generator function's body.
Do you see what makes the yield statement (or more accurately, generators) so special? Unlike the measly return keyword, yield is able to pass values to its caller and receive values from its caller all without terminating the function it lives in! (Of course, if you do wish to terminate a function -- or a generator -- it's handy to have the return keyword as well.) When a yield statement is encountered, the generator function merely pauses, and then picks back up right where it left off upon being sent another value. And send is just the interface for communicating with the inside of a generator function from outside it.
If we really want to break this push/pull/pipe analogy down as far as we can, we end up with the following pseudocode that really drives home that, aside from steps 1-5, yield and send are two sides of the same coin pipe:
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
The key transformation is that we have split x = yield y and value1 = g.send(value2) each into two statements: left_end.push(y) and x = left_end.pull(); and value1 = right_end.pull() and right_end.push(value2). There are two special cases of the yield keyword: x = yield and yield y. These are syntactic sugar, respectively, for x = yield None and _ = yield y # discarding value.
For specific details regarding the precise order in which values are sent through the pipe, see below.
What follows is a rather long concrete model of the above. First, it should first be noted that for any generator g, next(g) is exactly equivalent to g.send(None). With this in mind we can focus only on how send works and talk only about advancing the generator with send.
Suppose we have
def f(y): # This is the "generator function" referenced above
while True:
x = yield y
y = x
g = f(1)
g.send(None) # yields 1
g.send(2) # yields 2
Now, the definition of f roughly desugars to the following ordinary (non-generator) function:
def f(y):
bidirectional_pipe = BidirectionalPipe()
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
def impl():
initial_value = left_end.pull()
if initial_value is not None:
raise TypeError(
"can't send non-None value to a just-started generator"
)
while True:
left_end.push(y)
x = left_end.pull()
y = x
def send(value):
right_end.push(value)
return right_end.pull()
right_end.send = send
# This isn't real Python; normally, returning exits the function. But
# pretend that it's possible to return a value from a function and then
# continue execution -- this is exactly the problem that generators were
# designed to solve!
return right_end
impl()
The following has happened in this transformation of f:
We've moved the implementation into a nested function.
We've created a bidirectional pipe whose left_end will be accessed by the nested function and whose right_end will be returned and accessed by the outer scope -- right_end is what we know as the generator object.
Within the nested function, the very first thing we do is check that left_end.pull() is None, consuming a pushed value in the process.
Within the nested function, the statement x = yield y has been replaced by two lines: left_end.push(y) and x = left_end.pull().
We've defined the send function for right_end, which is the counterpart to the two lines we replaced the x = yield y statement with in the previous step.
In this fantasy world where functions can continue after returning, g is assigned right_end and then impl() is called. So in our example above, were we to follow execution line by line, what would happen is roughly the following:
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
y = 1 # from g = f(1)
# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks
# Receive the pushed value, None
initial_value = left_end.pull()
if initial_value is not None: # ok, `g` sent None
raise TypeError(
"can't send non-None value to a just-started generator"
)
left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off
# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()
# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes
# Receive the pushed value, 2
x = left_end.pull()
y = x # y == x == 2
left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off
# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()
x = left_end.pull()
# blocks until the next call to g.send
This maps exactly to the 16-step pseudocode above.
There are some other details, like how errors are propagated and what happens when you reach the end of the generator (the pipe is closed), but this should make clear how the basic control flow works when send is used.
Using these same desugaring rules, let's look at two special cases:
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
For the most part they desugar the same way as f, the only differences are how the yield statements are transformed:
def f1(x):
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
def f2():
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
In the first, the value passed to f1 is pushed (yielded) initially, and then all values pulled (sent) are pushed (yielded) right back. In the second, x has no value (yet) when it first come times to push, so an UnboundLocalError is raised.
These confused me too. Here is an example I made when trying to set up a generator which yields and accepts signals in alternating order (yield, accept, yield, accept)...
def echo_sound():
thing_to_say = '<Sound of wind on cliffs>'
while True:
thing_to_say = (yield thing_to_say)
thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
yield None # This is the return value of send.
gen = echo_sound()
print 'You are lost in the wilderness, calling for help.'
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
The output is:
You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
itr.send(None) is the same thing as next(itr) and what you're doing is giving the value given by yield in the generator.
Here's an example that clearly shows this, and how it can be used more practically.
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
num = iterator_towards()
for i in num:
print(i)
if i == 5:
num.send(0)
This will print:
0
1
2
3
4
5
3
2
1
0
The code at i == 5 tells it to send 0. This is not None in the iterator_towards and so it changes the value of dest. We then iterate towards 0.
However note, there is no value 4 after the value 5. This is because the nature of .send(0) is that it was yielded the 4 value and that was not printed.
If we add a continue we can re-yield the same value.
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
continue
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
Which will allow you to iterate a list but also dynamically send it new destination values onthefly.

What is the python syntax when a variable is after 'yield'? [duplicate]

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 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.
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.
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.
Some use cases for using generator and send()
Generators with send() allow:
remembering internal state of the execution
what step we are at
what is current status of our data
returning sequence of values
receiving sequence of inputs
Here are some use cases:
Watched attempt to follow a recipe
Let us have a recipe, which expects predefined set of inputs in some order.
We may:
create a watched_attempt instance from the recipe
let it get some inputs
with each input return information about what is currently in the pot
with each input check, that the input is the expected one (and fail if it is not)
def recipe():
pot = []
action = yield pot
assert action == ("add", "water")
pot.append(action[1])
action = yield pot
assert action == ("add", "salt")
pot.append(action[1])
action = yield pot
assert action == ("boil", "water")
action = yield pot
assert action == ("add", "pasta")
pot.append(action[1])
action = yield pot
assert action == ("decant", "water")
pot.remove("water")
action = yield pot
assert action == ("serve")
pot = []
yield pot
To use it, first create the watched_attempt instance:
>>> watched_attempt = recipe()
>>> watched_attempt.next()
[]
The call to .next() is necessary to start execution of the generator.
Returned value shows, our pot is currently empty.
Now do few actions following what the recipe expects:
>>> watched_attempt.send(("add", "water"))
['water']
>>> watched_attempt.send(("add", "salt"))
['water', 'salt']
>>> watched_attempt.send(("boil", "water"))
['water', 'salt']
>>> watched_attempt.send(("add", "pasta"))
['water', 'salt', 'pasta']
>>> watched_attempt.send(("decant", "water"))
['salt', 'pasta']
>>> watched_attempt.send(("serve"))
[]
As we see, the pot is finally empty.
In case, one would not follow the recipe, it would fail (what could be desired outcome of watched
attempt to cook something - just learning we did not pay enough attention when given instructions.
>>> watched_attempt = running.recipe()
>>> watched_attempt.next()
[]
>>> watched_attempt.send(("add", "water"))
['water']
>>> watched_attempt.send(("add", "pasta"))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))
/home/javl/sandbox/stack/send/running.py in recipe()
29
30 action = yield pot
---> 31 assert action == ("add", "salt")
32 pot.append(action[1])
33
AssertionError:
Notice, that:
there is linear sequence of expected steps
the steps may differ (some are removing, some are adding to the pot)
we manage to do all that by a functions/generator - no need to use complex class or similar
strutures.
Running totals
We may use the generator to keep track of running total of values sent to it.
Any time we add a number, count of inputs and total sum is returned (valid for
the moment previous input was send into it).
from collections import namedtuple
RunningTotal = namedtuple("RunningTotal", ["n", "total"])
def runningtotals(n=0, total=0):
while True:
delta = yield RunningTotal(n, total)
if delta:
n += 1
total += delta
if __name__ == "__main__":
nums = [9, 8, None, 3, 4, 2, 1]
bookeeper = runningtotals()
print bookeeper.next()
for num in nums:
print num, bookeeper.send(num)
The output would look like:
RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)
The send method implements coroutines.
If you haven't encountered Coroutines they are tricky to wrap your head around because they change the way a program flows. You can read a good tutorial for more details.
The word "yield" has two meanings: to produce something (e.g., to yield corn), and to halt to let someone/thing else continue (e.g., cars yielding to pedestrians). Both definitions apply to Python's yield keyword; what makes generator functions special is that unlike in regular functions, values can be "returned" to the caller while merely pausing, not terminating, a generator function.
It is easiest to imagine a generator as one end of a bidirectional pipe with a "left" end and a "right" end; this pipe is the medium over which values are sent between the generator itself and the generator function's body. Each end of the pipe has two operations: push, which sends a value and blocks until the other end of the pipe pulls the value, and returns nothing; and pull, which blocks until the other end of the pipe pushes a value, and returns the pushed value. At runtime, execution bounces back and forth between the contexts on either side of the pipe -- each side runs until it sends a value to the other side, at which point it halts, lets the other side run, and waits for a value in return, at which point the other side halts and it resumes. In other words, each end of the pipe runs from the moment it receives a value to the moment it sends a value.
The pipe is functionally symmetric, but -- by convention I'm defining in this answer -- the left end is only available inside the generator function's body and is accessible via the yield keyword, while the right end is the generator and is accessible via the generator's send function. As singular interfaces to their respective ends of the pipe, yield and send do double duty: they each both push and pull values to/from their ends of the pipe, yield pushing rightward and pulling leftward while send does the opposite. This double duty is the crux of the confusion surrounding the semantics of statements like x = yield y. Breaking yield and send down into two explicit push/pull steps will make their semantics much more clear:
Suppose g is the generator. g.send pushes a value leftward through the right end of the pipe.
Execution within the context of g pauses, allowing the generator function's body to run.
The value pushed by g.send is pulled leftward by yield and received on the left end of the pipe. In x = yield y, x is assigned to the pulled value.
Execution continues within the generator function's body until the next line containing yield is reached.
yield pushes a value rightward through the left end of the pipe, back up to g.send. In x = yield y, y is pushed rightward through the pipe.
Execution within the generator function's body pauses, allowing the outer scope to continue where it left off.
g.send resumes and pulls the value and returns it to the user.
When g.send is next called, go back to Step 1.
While cyclical, this procedure does have a beginning: when g.send(None) -- which is what next(g) is short for -- is first called (it is illegal to pass something other than None to the first send call). And it may have an end: when there are no more yield statements to be reached in the generator function's body.
Do you see what makes the yield statement (or more accurately, generators) so special? Unlike the measly return keyword, yield is able to pass values to its caller and receive values from its caller all without terminating the function it lives in! (Of course, if you do wish to terminate a function -- or a generator -- it's handy to have the return keyword as well.) When a yield statement is encountered, the generator function merely pauses, and then picks back up right where it left off upon being sent another value. And send is just the interface for communicating with the inside of a generator function from outside it.
If we really want to break this push/pull/pipe analogy down as far as we can, we end up with the following pseudocode that really drives home that, aside from steps 1-5, yield and send are two sides of the same coin pipe:
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
The key transformation is that we have split x = yield y and value1 = g.send(value2) each into two statements: left_end.push(y) and x = left_end.pull(); and value1 = right_end.pull() and right_end.push(value2). There are two special cases of the yield keyword: x = yield and yield y. These are syntactic sugar, respectively, for x = yield None and _ = yield y # discarding value.
For specific details regarding the precise order in which values are sent through the pipe, see below.
What follows is a rather long concrete model of the above. First, it should first be noted that for any generator g, next(g) is exactly equivalent to g.send(None). With this in mind we can focus only on how send works and talk only about advancing the generator with send.
Suppose we have
def f(y): # This is the "generator function" referenced above
while True:
x = yield y
y = x
g = f(1)
g.send(None) # yields 1
g.send(2) # yields 2
Now, the definition of f roughly desugars to the following ordinary (non-generator) function:
def f(y):
bidirectional_pipe = BidirectionalPipe()
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
def impl():
initial_value = left_end.pull()
if initial_value is not None:
raise TypeError(
"can't send non-None value to a just-started generator"
)
while True:
left_end.push(y)
x = left_end.pull()
y = x
def send(value):
right_end.push(value)
return right_end.pull()
right_end.send = send
# This isn't real Python; normally, returning exits the function. But
# pretend that it's possible to return a value from a function and then
# continue execution -- this is exactly the problem that generators were
# designed to solve!
return right_end
impl()
The following has happened in this transformation of f:
We've moved the implementation into a nested function.
We've created a bidirectional pipe whose left_end will be accessed by the nested function and whose right_end will be returned and accessed by the outer scope -- right_end is what we know as the generator object.
Within the nested function, the very first thing we do is check that left_end.pull() is None, consuming a pushed value in the process.
Within the nested function, the statement x = yield y has been replaced by two lines: left_end.push(y) and x = left_end.pull().
We've defined the send function for right_end, which is the counterpart to the two lines we replaced the x = yield y statement with in the previous step.
In this fantasy world where functions can continue after returning, g is assigned right_end and then impl() is called. So in our example above, were we to follow execution line by line, what would happen is roughly the following:
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
y = 1 # from g = f(1)
# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks
# Receive the pushed value, None
initial_value = left_end.pull()
if initial_value is not None: # ok, `g` sent None
raise TypeError(
"can't send non-None value to a just-started generator"
)
left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off
# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()
# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes
# Receive the pushed value, 2
x = left_end.pull()
y = x # y == x == 2
left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off
# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()
x = left_end.pull()
# blocks until the next call to g.send
This maps exactly to the 16-step pseudocode above.
There are some other details, like how errors are propagated and what happens when you reach the end of the generator (the pipe is closed), but this should make clear how the basic control flow works when send is used.
Using these same desugaring rules, let's look at two special cases:
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
For the most part they desugar the same way as f, the only differences are how the yield statements are transformed:
def f1(x):
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
def f2():
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
In the first, the value passed to f1 is pushed (yielded) initially, and then all values pulled (sent) are pushed (yielded) right back. In the second, x has no value (yet) when it first come times to push, so an UnboundLocalError is raised.
These confused me too. Here is an example I made when trying to set up a generator which yields and accepts signals in alternating order (yield, accept, yield, accept)...
def echo_sound():
thing_to_say = '<Sound of wind on cliffs>'
while True:
thing_to_say = (yield thing_to_say)
thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
yield None # This is the return value of send.
gen = echo_sound()
print 'You are lost in the wilderness, calling for help.'
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
The output is:
You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
itr.send(None) is the same thing as next(itr) and what you're doing is giving the value given by yield in the generator.
Here's an example that clearly shows this, and how it can be used more practically.
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
num = iterator_towards()
for i in num:
print(i)
if i == 5:
num.send(0)
This will print:
0
1
2
3
4
5
3
2
1
0
The code at i == 5 tells it to send 0. This is not None in the iterator_towards and so it changes the value of dest. We then iterate towards 0.
However note, there is no value 4 after the value 5. This is because the nature of .send(0) is that it was yielded the 4 value and that was not printed.
If we add a continue we can re-yield the same value.
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
continue
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
Which will allow you to iterate a list but also dynamically send it new destination values onthefly.

Using generator send() within a for loop

I implemented graph traversal as a generator function which yields the node being visited.
Sometimes the user needs to tell the traversal function that the edges outgoing from a particular node shouldn't be followed; in order to support that, the traversal checks the value sent back to it (using generator send() method), and if it's True, regards the node as a leaf for traversal purposes.
The problem is that the simplest user loop is kinda long:
# simplified thanks to #tobias_k
# bfs is the traversal generator function
traversal = bfs(g, start_node)
try:
n = next(traversal)
while True:
# process(n) returns True if don't want to follow edges out of n
n = traversal.send(process(n))
except StopIteration:
pass
Is there any way to improve this?
I thought something like this should work:
for n in bfs(g, start_node):
???.send(process(n))
but I feel I'm missing the knowledge of some python syntax.
I don't see a way to do this in a regular for loop. However, you could create another generator, that iterates another generator, using some "follow-function" to determine whether to follow the current element, thus encapsulating the tricky parts of your code into a separate function.
def checking_generator(generator, follow_function):
try:
x = next(generator)
while True:
yield x
x = generator.send(follow_function(x))
except StopIteration:
pass
for n in checking_generator(bfs(g, start_node), process):
print(n)
I discovered that my question would have had a one-line answer, using the extended "continue" statement proposed in the earlier version of PEP 342:
for n in bfs(g, start_node):
continue process(n)
However, while PEP 342 was accepted, that particular feature was withdrawn after this June 2005 discussion between Raymond and Guido:
Raymond Hettinger said:
Let me go on record as a strong -1 for "continue EXPR". The
for-loop is our most basic construct and is easily understood in its
present form. The same can be said for "continue" and "break" which
have the added advantage of a near zero learning curve for people
migrating from other languages.
Any urge to complicate these basic statements should be seriously
scrutinized and held to high standards of clarity, explainability,
obviousness, usefulness, and necessity. IMO, it fails most of those
tests.
I would not look forward to explaining "continue EXPR" in the tutorial
and think it would stand out as an anti-feature.
[...] The correct argument against "continue EXPR" is that there
are no use cases yet; if there were a good use case, the explanation
would follow easily.
Guido
If python core developers have since changed their mind about the usefulness of extended "continue", perhaps this could be reintroduced into a future PEP. But, given a nearly identical use case as in this question was already discussed in the quoted thread, and wasn't found persuasive, it seems unlikely.
To simplify the client code, you could use an ordinary bsf() generator and check node.isleaf attribute in it:
for node in bfs(g, start_node):
node.isleaf = process(node) # don't follow if `process()` returns True
The disadvantage is that node is mutable. Or you have to pass a shared data structure that tracks leaf nodes: leaf[node] = process(node) where leaf dictionary is passed into bfs() earlier.
If you want to use .send() method explicitly; you have to handle StopIteration. See PEP 479 -- Change StopIteration handling inside generators. You could hide it in a helper function:
def traverse(tree_generator, visitor):
try:
node = next(tree_generator)
while True:
node = tree_generator.send(visitor(node))
except StopIteration:
pass
Example:
traverse(bfs(g, start_node), process)
I don't see this as a frequent use case, consider this as the original generator:
def original_gen():
for x in range(10):
should_break = yield x
if should_break:
break
If the value of should_break is always calculated based on some function call with x then why not just write the generator like this:
def processing_gen(check_f):
for x in range(10):
yield x
should_break = check_f(x)
if should_break:
break
However I usually think of the code that processes the generated values as being written inside the loop (otherwise what is the point of having a loop at all?)
What it really seems you want to do is create a generator where calling the __next__ method really implies send(process(LAST_VALUE)) which can be implemented with a class:
class Followup_generator(): #feel free to use a better name
def __init__(self,generator,following_function):
self.gen = generator
self.process_f = following_function
def __iter__(self):
return self
def __next__(self):
if hasattr(self,"last_value"):
return self.send(self.process_f(self.last_value))
else:
self.last_value = next(self.gen)
return self.last_value
def send(self,arg):
self.last_value = self.gen.send(arg)
return self.last_value
def __getattr__(self,attr):
"forward other lookups to the generator (.throw etc.)"
return getattr(self.gen, attr)
# call signature is the exact same as #tobias_k's checking_generator
traversal = Followup_generator(bfs(g, start_node), process)
for n in traversal:
print(n)
n = traversal.send(DATA) #you'd be able to send extra values to it
However this still doesn't see this as frequently used, I'd be perfectly fine with a while loop, although I'd put the .send call at the top:
traversal = bfs(g, start_node)
send_value = None
while True:
n = traversal.send(send_value)
#code for loop, ending in calculating the next send_value
send_value = process(n)
And you might wrap that in a try: ... except StopIteration:pass although I find that simply waiting for an error to raise is better expressed with a context manager:
class Catch:
def __init__(self,exc_type):
if issubclass(exc_type,BaseException):
self.catch_type = exc_type
else:
raise TypeError("can only catch Exceptions")
def __enter__(self):
return self
def __exit__(self,exc_type,err, tb):
if issubclass(exc_type, self.catch_type):
self.err = err
return True
with Catch(StopIteration):
traversal = bfs(g, start_node)
send_value = None
while True:
n = traversal.send(send_value)
#code for loop, ending in calculating the next send_value
send_value = process(n)
Probably this is the answer to the question from the thread's topic.
Take a look at the additional empty yields statements inside the traversal function and custom send function, that does the magical job.
# tested with Python 3.7
def traversal(n):
for i in range(n):
yield i, '%s[%s] %s' % (' ' * (4 - n), n, i)
stop = yield
if stop:
yield # here's the first part of the magic
else:
yield # the same as above
yield from traversal(int(n / 2))
def send(generator, value):
next(generator) # here's the second part of the magic
generator.send(value)
g = traversal(4)
for i, (num, msg) in enumerate(g):
print('>', i, msg)
stop = num % 2 == 0
send(g, stop)
I've written a small class SettableGenerator which uses a method to receive the value to be send and then forwards it to the actual generator when __next__ is invoked.
With this you can write:
gen = SettableGenerator(bfs(g, start_node))
for n in gen:
gen.set(process(n))
Let's consider the following generator. It generates numbers from 0 to 9. For every generated number, it gets an input and stores it into ret:
def count_to_nine():
# Output: numbers from 0 to 9
# Input: converted numbers
ret = []
for i in range(10):
# Yield a number, get something back
val = (yield i)
# Remember that "something"
ret.append(val)
return ret
You can, indeed, iterate it using next() + send(),
but the best way is to iterate using send() alone:
g = count_to_nine()
value = None # to make sure that the first send() gives a None
while True:
value = g.send(value) # send the previously generated value, get a new one
value = f'#{value}'
Here's the result:
StopIteration: ['#0', '#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8', '#9']
If you want that output, catch the StopIteration and get the result from it.
Cheers!

What is the purpose of the "send" function on Python generators?

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 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.
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.
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.
Some use cases for using generator and send()
Generators with send() allow:
remembering internal state of the execution
what step we are at
what is current status of our data
returning sequence of values
receiving sequence of inputs
Here are some use cases:
Watched attempt to follow a recipe
Let us have a recipe, which expects predefined set of inputs in some order.
We may:
create a watched_attempt instance from the recipe
let it get some inputs
with each input return information about what is currently in the pot
with each input check, that the input is the expected one (and fail if it is not)
def recipe():
pot = []
action = yield pot
assert action == ("add", "water")
pot.append(action[1])
action = yield pot
assert action == ("add", "salt")
pot.append(action[1])
action = yield pot
assert action == ("boil", "water")
action = yield pot
assert action == ("add", "pasta")
pot.append(action[1])
action = yield pot
assert action == ("decant", "water")
pot.remove("water")
action = yield pot
assert action == ("serve")
pot = []
yield pot
To use it, first create the watched_attempt instance:
>>> watched_attempt = recipe()
>>> watched_attempt.next()
[]
The call to .next() is necessary to start execution of the generator.
Returned value shows, our pot is currently empty.
Now do few actions following what the recipe expects:
>>> watched_attempt.send(("add", "water"))
['water']
>>> watched_attempt.send(("add", "salt"))
['water', 'salt']
>>> watched_attempt.send(("boil", "water"))
['water', 'salt']
>>> watched_attempt.send(("add", "pasta"))
['water', 'salt', 'pasta']
>>> watched_attempt.send(("decant", "water"))
['salt', 'pasta']
>>> watched_attempt.send(("serve"))
[]
As we see, the pot is finally empty.
In case, one would not follow the recipe, it would fail (what could be desired outcome of watched
attempt to cook something - just learning we did not pay enough attention when given instructions.
>>> watched_attempt = running.recipe()
>>> watched_attempt.next()
[]
>>> watched_attempt.send(("add", "water"))
['water']
>>> watched_attempt.send(("add", "pasta"))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))
/home/javl/sandbox/stack/send/running.py in recipe()
29
30 action = yield pot
---> 31 assert action == ("add", "salt")
32 pot.append(action[1])
33
AssertionError:
Notice, that:
there is linear sequence of expected steps
the steps may differ (some are removing, some are adding to the pot)
we manage to do all that by a functions/generator - no need to use complex class or similar
strutures.
Running totals
We may use the generator to keep track of running total of values sent to it.
Any time we add a number, count of inputs and total sum is returned (valid for
the moment previous input was send into it).
from collections import namedtuple
RunningTotal = namedtuple("RunningTotal", ["n", "total"])
def runningtotals(n=0, total=0):
while True:
delta = yield RunningTotal(n, total)
if delta:
n += 1
total += delta
if __name__ == "__main__":
nums = [9, 8, None, 3, 4, 2, 1]
bookeeper = runningtotals()
print bookeeper.next()
for num in nums:
print num, bookeeper.send(num)
The output would look like:
RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)
The send method implements coroutines.
If you haven't encountered Coroutines they are tricky to wrap your head around because they change the way a program flows. You can read a good tutorial for more details.
The word "yield" has two meanings: to produce something (e.g., to yield corn), and to halt to let someone/thing else continue (e.g., cars yielding to pedestrians). Both definitions apply to Python's yield keyword; what makes generator functions special is that unlike in regular functions, values can be "returned" to the caller while merely pausing, not terminating, a generator function.
It is easiest to imagine a generator as one end of a bidirectional pipe with a "left" end and a "right" end; this pipe is the medium over which values are sent between the generator itself and the generator function's body. Each end of the pipe has two operations: push, which sends a value and blocks until the other end of the pipe pulls the value, and returns nothing; and pull, which blocks until the other end of the pipe pushes a value, and returns the pushed value. At runtime, execution bounces back and forth between the contexts on either side of the pipe -- each side runs until it sends a value to the other side, at which point it halts, lets the other side run, and waits for a value in return, at which point the other side halts and it resumes. In other words, each end of the pipe runs from the moment it receives a value to the moment it sends a value.
The pipe is functionally symmetric, but -- by convention I'm defining in this answer -- the left end is only available inside the generator function's body and is accessible via the yield keyword, while the right end is the generator and is accessible via the generator's send function. As singular interfaces to their respective ends of the pipe, yield and send do double duty: they each both push and pull values to/from their ends of the pipe, yield pushing rightward and pulling leftward while send does the opposite. This double duty is the crux of the confusion surrounding the semantics of statements like x = yield y. Breaking yield and send down into two explicit push/pull steps will make their semantics much more clear:
Suppose g is the generator. g.send pushes a value leftward through the right end of the pipe.
Execution within the context of g pauses, allowing the generator function's body to run.
The value pushed by g.send is pulled leftward by yield and received on the left end of the pipe. In x = yield y, x is assigned to the pulled value.
Execution continues within the generator function's body until the next line containing yield is reached.
yield pushes a value rightward through the left end of the pipe, back up to g.send. In x = yield y, y is pushed rightward through the pipe.
Execution within the generator function's body pauses, allowing the outer scope to continue where it left off.
g.send resumes and pulls the value and returns it to the user.
When g.send is next called, go back to Step 1.
While cyclical, this procedure does have a beginning: when g.send(None) -- which is what next(g) is short for -- is first called (it is illegal to pass something other than None to the first send call). And it may have an end: when there are no more yield statements to be reached in the generator function's body.
Do you see what makes the yield statement (or more accurately, generators) so special? Unlike the measly return keyword, yield is able to pass values to its caller and receive values from its caller all without terminating the function it lives in! (Of course, if you do wish to terminate a function -- or a generator -- it's handy to have the return keyword as well.) When a yield statement is encountered, the generator function merely pauses, and then picks back up right where it left off upon being sent another value. And send is just the interface for communicating with the inside of a generator function from outside it.
If we really want to break this push/pull/pipe analogy down as far as we can, we end up with the following pseudocode that really drives home that, aside from steps 1-5, yield and send are two sides of the same coin pipe:
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
The key transformation is that we have split x = yield y and value1 = g.send(value2) each into two statements: left_end.push(y) and x = left_end.pull(); and value1 = right_end.pull() and right_end.push(value2). There are two special cases of the yield keyword: x = yield and yield y. These are syntactic sugar, respectively, for x = yield None and _ = yield y # discarding value.
For specific details regarding the precise order in which values are sent through the pipe, see below.
What follows is a rather long concrete model of the above. First, it should first be noted that for any generator g, next(g) is exactly equivalent to g.send(None). With this in mind we can focus only on how send works and talk only about advancing the generator with send.
Suppose we have
def f(y): # This is the "generator function" referenced above
while True:
x = yield y
y = x
g = f(1)
g.send(None) # yields 1
g.send(2) # yields 2
Now, the definition of f roughly desugars to the following ordinary (non-generator) function:
def f(y):
bidirectional_pipe = BidirectionalPipe()
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
def impl():
initial_value = left_end.pull()
if initial_value is not None:
raise TypeError(
"can't send non-None value to a just-started generator"
)
while True:
left_end.push(y)
x = left_end.pull()
y = x
def send(value):
right_end.push(value)
return right_end.pull()
right_end.send = send
# This isn't real Python; normally, returning exits the function. But
# pretend that it's possible to return a value from a function and then
# continue execution -- this is exactly the problem that generators were
# designed to solve!
return right_end
impl()
The following has happened in this transformation of f:
We've moved the implementation into a nested function.
We've created a bidirectional pipe whose left_end will be accessed by the nested function and whose right_end will be returned and accessed by the outer scope -- right_end is what we know as the generator object.
Within the nested function, the very first thing we do is check that left_end.pull() is None, consuming a pushed value in the process.
Within the nested function, the statement x = yield y has been replaced by two lines: left_end.push(y) and x = left_end.pull().
We've defined the send function for right_end, which is the counterpart to the two lines we replaced the x = yield y statement with in the previous step.
In this fantasy world where functions can continue after returning, g is assigned right_end and then impl() is called. So in our example above, were we to follow execution line by line, what would happen is roughly the following:
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
y = 1 # from g = f(1)
# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks
# Receive the pushed value, None
initial_value = left_end.pull()
if initial_value is not None: # ok, `g` sent None
raise TypeError(
"can't send non-None value to a just-started generator"
)
left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off
# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()
# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes
# Receive the pushed value, 2
x = left_end.pull()
y = x # y == x == 2
left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off
# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()
x = left_end.pull()
# blocks until the next call to g.send
This maps exactly to the 16-step pseudocode above.
There are some other details, like how errors are propagated and what happens when you reach the end of the generator (the pipe is closed), but this should make clear how the basic control flow works when send is used.
Using these same desugaring rules, let's look at two special cases:
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
For the most part they desugar the same way as f, the only differences are how the yield statements are transformed:
def f1(x):
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
def f2():
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
In the first, the value passed to f1 is pushed (yielded) initially, and then all values pulled (sent) are pushed (yielded) right back. In the second, x has no value (yet) when it first come times to push, so an UnboundLocalError is raised.
These confused me too. Here is an example I made when trying to set up a generator which yields and accepts signals in alternating order (yield, accept, yield, accept)...
def echo_sound():
thing_to_say = '<Sound of wind on cliffs>'
while True:
thing_to_say = (yield thing_to_say)
thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
yield None # This is the return value of send.
gen = echo_sound()
print 'You are lost in the wilderness, calling for help.'
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)
The output is:
You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
itr.send(None) is the same thing as next(itr) and what you're doing is giving the value given by yield in the generator.
Here's an example that clearly shows this, and how it can be used more practically.
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
num = iterator_towards()
for i in num:
print(i)
if i == 5:
num.send(0)
This will print:
0
1
2
3
4
5
3
2
1
0
The code at i == 5 tells it to send 0. This is not None in the iterator_towards and so it changes the value of dest. We then iterate towards 0.
However note, there is no value 4 after the value 5. This is because the nature of .send(0) is that it was yielded the 4 value and that was not printed.
If we add a continue we can re-yield the same value.
def iterator_towards(dest=100):
value = 0
while True:
n = yield value
if n is not None:
dest = n
continue
if dest > value:
value += 1
elif dest < value:
value -= 1
else:
return
Which will allow you to iterate a list but also dynamically send it new destination values onthefly.

How can I write a Python decorator to increase stackdepth?

BACKGROUND
When playing around, I often write simple recursive functions looking something like:
def f(a,b):
if a>=0 and b>=0:
return min( f(a-1,b) , f(b,a-1) ) # + some cost that depends on a,b
else:
return 0
(For example, when computing weighted edit distances, or evaluating recursively defined mathematical formulas.)
I then use a memoizing decorator to cache the results automatically.
PROBLEM
When I try something like f(200,10) I get:
RuntimeError: maximum recursion depth exceeded
This is as expected because the recursive implementation exhausts Python's stack space/ recursion limits.
WORKAROUNDS
I usually work around this problem by one of:
Increasing recursion limit with sys.setrecursionlimit (only works up to about 1000 depth)
Using a for loop to fill up the cache for smaller values
Changing the function to use a list as a manual stack (via append and pop calls) (in other words, moving from a recursive implementation to an iterative one)
Using an alternative programming language
but I find all of these quite error prone.
QUESTION
Is there a way to write an #Bigstack decorator that would simulate the effect of having a really big stack?
Note that my functions normally make several recursive function calls so this is not the same as tail recursion - I really do want to save all the internal state of each function on the stack.
WHAT I'VE TRIED
I've been thinking about using a list of generator expressions as my stack. By probing the stackframe I could work out when the function has been called recursively and then trigger an exception to return to the decorator code. However, I can't work out a way of gluing these ideas together to make anything that actually works.
Alternatively, I could try accessing the abstract syntax tree for the function and try transforming calls to recursive functions to yield statements, but this seems like it's heading in the wrong direction.
Any suggestions?
EDIT
It certainly looks like I am misusing Python, but another approach I have been considering is to use a different thread for each block of, say, 500 stack frames and then insert queues between each consecutive pair of threads - one queue for arguments, and another queue for return values. (Each queue will have at most one entry in it.) I think this probably doesn't work for some reason - but I'll probably only work out why after I've tried to implement it.
To get around the recursion limit, you can catch the RuntimeError exception to detect when you've run out of stack space, and then return a continuation-ish function that, when called, restarts the recursion at the level where you ran out of space. Call this (and its return value, and so on) until you get a value, then try again from the top. Once you've memoized the lower levels, the higher levels won't run into a recursion limit, so eventually this will work. Put the repeated-calling-until-it-works in a wrapper function. Basically it's a lazy version of your warming-up-the-cache idea.
Here's an example with a simple recursive "add numbers from 1 to n inclusive" function.
import functools
def memoize(func):
cache = {}
#functools.wraps(func)
def wrapper(*args, **kwargs):
key = args, tuple(sorted(kwargs.items()))
if key in cache:
return cache[key]
else:
result = func(*args, **kwargs)
if not callable(result):
cache[key] = result
return result
return wrapper
#memoize
def _addup(n):
if n < 2:
return n
else:
try:
result = _addup(n - 1)
except RuntimeError:
return lambda: _addup(n)
else:
return result if callable(result) else result + n
def addup(n):
result = _addup(n)
while callable(result):
while callable(result):
result = result()
result = _addup(n)
return result
assert addup(5000) == sum(xrange(5001))
Rather than returning the lambda function all the way back up the call chain, we can raise an exception to short-circuit that, which both improves performance and simplifies the code:
# memoize function as above, or you can probably use functools.lru_cache
class UnwindStack(Exception):
pass
#memoize
def _addup(n):
if n < 2:
return n
else:
try:
return _addup(n - 1) + n
except RuntimeError:
raise UnwindStack(lambda: _addup(n))
def _try(func, *args, **kwargs):
try:
return func(*args, **kwargs)
except UnwindStack as e:
return e[0]
def addup(n):
result = _try(_addup, n)
while callable(result):
while callable(result):
result = _try(result)
result = _try(_addup, n)
return result
This remains pretty inelegant, though, and still has a fair amount of overhead, and I can't imagine how you'd make a decorator out it. Python isn't really suited to this kind of thing, I guess.
Here's an implementation that uses a list of generator expressions as the stack:
def run_stackless(frame):
stack, return_stack = [(False, frame)], []
while stack:
active, frame = stack.pop()
action, res = frame.send(return_stack.pop() if active else None)
if action == 'call':
stack.extend([(True, frame), (False, res)])
elif action == 'tail':
stack.append((False, res))
elif action == 'return':
return_stack.append(res)
else:
raise ValueError('Unknown action', action)
return return_stack.pop()
To use it you need to transform the recursive function according to the following rules:
return expr -> yield 'return', expr
recursive_call(args...) -> (yield 'call', recursive_call(args...))
return recursive_call(args...) -> yield 'tail', recursive_call(args...)
For example, with the cost function as a * b, your function becomes:
def f(a,b):
if a>=0 and b>=0:
yield 'return', min((yield 'call', f(a-1,b)),
(yield 'call', f(b,a-1))) + (a * b)
else:
yield 'return', 0
Testing:
In [140]: run_stackless(g(30, 4))
Out[140]: 410
In Python 2.6.2 it appears to offer a ~8-10x performance hit compared to direct calls.
The tail action is for tail recursion:
def factorial(n):
acc = [1]
def fact(n):
if n == 0:
yield 'return', 0
else:
acc[0] *= n
yield 'tail', fact(n - 1)
run_stackless(fact(n))
return acc[0]
The transformation to generator-recursive style is fairly easy, and could probably be done as a bytecode hack.
This approach combines memoisation and increased stack depth into a single decorator.
I generate a pool of threads with each thread responsible for 64 levels of the stack.
Threads are only created once and resued (but currently never deleted).
Queues are used to pass information between threads, although note that only the thread corresponding to the current stack depth will actually have work to do.
My experiments suggest this adds around 10% overhead for a simple recursive function (and should be less for more complicated functions).
import threading,Queue
class BigstackThread(threading.Thread):
def __init__(self,send,recv,func):
threading.Thread.__init__( self )
self.daemon = True
self.send = send
self.recv = recv
self.func = func
def run(self):
while 1:
args = self.send.get()
v = self.func(*args)
self.recv.put(v)
class Bigstack(object):
def __init__(self,func):
self.func = func
self.cache = {}
self.depth = 0
self.threadpool = {}
def __call__(self,*args):
if args in self.cache:
return self.cache[args]
self.depth+=1
if self.depth&63:
v = self.func(*args)
else:
T=self.threadpool
if self.depth not in T:
send = Queue.Queue(1)
recv = Queue.Queue(1)
t = BigstackThread(send,recv,self)
T[self.depth] = send,recv,t
t.start()
else:
send,recv,_ = T[self.depth]
send.put(args)
v = recv.get()
self.depth-=1
self.cache[args]=v
return v
#Bigstack
def f(a,b):
if a>=0 and b>=0:
return min(f(a-1,b),f(b-1,a))+1
return 0

Categories

Resources