[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)
answer is 67
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)
answer is 73
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)
answer is 86
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)
answer is 95
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



More information about the Python-ideas mailing list