[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