# [Python-ideas] PEP on yield-from, send example

Bruce Frederiksen dangyogi at gmail.com
Sun Feb 15 21:12:20 CET 2009

```Steven D'Aprano wrote:
> If I've understood the protoPEP, it wraps four distinct pieces of
> functionality:
>
> "yield from" pass-through
> pass-through for send
> pass-through for throw
> pass-through for close
>
> I think each one needs to be justified, or at least explained,
> individually. I'm afraid I'm not even clear on what pass-through for
> send/throw/close would even mean, let alone why they would be useful.
Here's the send example.

I want to write a function that plays "guess this number" by making
successive guesses and getting a high/low response.  My first version
will generate random guesses:

def rand_guesser(limit):
lo = 0              # answer is > lo
hi = limit + 1  # answer is < hi
num_tries = 0
while lo + 2 < hi:
guess = random.randint(lo + 1, hi - 1)
num_tries += 1
result = yield guess
if result == 0: break
if result < 0: lo = guess
else: hi = guess
else:
guess = lo + 1
print "rand_guesser: got", guess, "in", num_tries, "tries"

and then the function that calls it:

def test(guesser, limit):
n = random.randint(1, limit)
print "the secret number is", n
try:
guess = guesser.next()
while True:
print "got", guess
guess = guesser.send(cmp(guess, n))
except StopIteration:
pass   # guesser.close() isn't necessary if we got StopIteration,
# because the generator has already finalized.

>>> test(rand_guesser(100), 100)
got 33
got 81
got 69
got 47
got 56
got 68
got 58
got 62
got 64
got 67
rand_guesser: got 67 in 10 tries

So far, so good.  But how does binary_search compare with random_guesser?

def binary_search(limit):
lo = 0
hi = limit + 1
num_tries = 0
while lo + 2 < hi:
guess = (hi + lo) // 2
num_tries += 1
result = yield guess
if result == 0: break
if result < 0: lo = guess
else: hi = guess
else:
guess = lo + 1
print "binary_search: got", guess, "in", num_tries, "tries"

>>> test(binary_search(100), 100)
got 50
got 75
got 62
got 68
got 71
got 73
binary_search: got 73 in 6 tries

Hmmm, but compare these, I need to run them on the same answer number.
I know, I can just chain them together.  Then after test will just see
both sets of guesses back to back...  Another obvious choice for
itertools.chain!

>>> test(itertools.chain(random_guesser(100), binary_search(100)), 100)
got 62
Traceback (most recent call last):
File "throw2.py", line 134, in <module>
test(itertools.chain(rand_guesser(100), binary_search(100)), 100)
File "throw2.py", line 128, in test
guess = guesser.send(cmp(guess, n))
AttributeError: 'itertools.chain' object has no attribute 'send'

Oops, that's right, itertools.chain doesn't play nicely with advanced
generators... :-(

So I guess I have to write my own intermediate multi_guesser...
Luckily, we have yield from!

def multi_guesser(l, limit):
for gen in l:
yield from gen(limit)

What does yield from do?  It sets multi_guesser aside so that test can
communicate directly with each gen.  Objects yielded by the gen go
directly back to test.  And I would expect that objects sent from test
(with send) would go directly to the gen.  If that's the case, this
works fine!  If not, then I'm sad again and have to do something like:

def multi_guesser(l, limit):
for gen in l:
g = gen(limit)
try:
guess = g.next()
while True:
guess = g.send((yield guess))
except StopIteration:
pass

Which one do you think is more pythonic?  Which one would you rather get
stuck maintaining?  (Personally, I'd vote for itertools.chain!)

>>> test(multi_guesser((rand_guesser, binary_search), 100), 100)
got 39
got 99
got 80
got 98
got 93
got 94
got 97
got 96
rand_guesser: got 95 in 8 tries
got 50
got 75
got 88
got 94
got 97
got 95
binary_search: got 95 in 6 tries

-bruce frederiksen

```