[Python-ideas] Possible PEP 380 tweak

Carl M. Johnson cmjohnson.mailinglist at gmail.com
Fri Oct 29 05:17:14 CEST 2010


On Thu, Oct 28, 2010 at 4:21 PM, Guido van Rossum <guido at python.org> wrote:
> On Thu, Oct 28, 2010 at 6:17 PM, Nick Coghlan <ncoghlan at gmail.com> wrote:
>> To use a toy example:
>>
>>  # Even this toy framework needs a little structure
>>  class EndSum(Exception): pass
>>
>>  def gsum():
>>    # Sums sent values until EndSum or GeneratorExit are thrown in
>>    tally = 0
>>    try:
>>      while 1:
>>        tally += yield
>>    except (EndSum, GeneratorExit):
>>      pass
>>    return x
>
> You meant return tally. Right?
>
>>  def average_sums():
>>    # Advances to a new sum when EndSum is thrown in
>>    # Finishes the last sum and averages them all when GeneratorExit
>> is thrown in
>>    sums = []
>>    try:
>>      while 1:
>>        sums.append(yield from gsum())
>>    except GeneratorExit as ex:
>>      # Our proposed expansion tweak is to enable the next line
>>      sums.append(ex.args[0])
>>    return sum(sums) / len(sums)
>

This toy example is a little confusing to me because it has typos…
which is natural when one is writing a program without being able to
run it to debug it. So, I wrote a version of the accumulator/averager
that will work in Python 2.7 (and I think 3, but I didn't test it):


class ReturnValue(Exception): pass

def prime_pump(gen):
    def f(*args, **kwargs):
        g = gen(*args, **kwargs)
        next(g)
        return g
    return f

@prime_pump
def accumulator():
    total = 0
    length = 0
    try:
        while 1:
            value = yield
            total += value
            length += 1
            print(length, value, total)
    except GeneratorExit:
        r = ReturnValue()
        r.total = total
        r.length = length
        raise r

@contextmanager
def get_sum(it):
    try:
        it.close()
    except ReturnValue as r:
        yield r.total

@contextmanager
def get_average(it):
    try:
        it.close()
    except ReturnValue as r:
        yield r.total / r.length


def main():
    running_total = accumulator()
    sums = accumulator()
    running_total.send(6) #For example, whatever
    running_total.send(7)
    with get_sum(running_total) as first_sum:
        sums.send(first_sum)

    running_total = accumulator() #Zero it out

    running_total.send(2) #For example, whatever
    running_total.send(2)
    running_total.send(5)
    running_total.send(8)

    with get_sum(running_total) as second_sum:
        sums.send(second_sum)

    #Get the average of the sums
    with get_average(sums) as r:
        return r

main()

So, I guess the question I have is how will the proposed extensions to
the language make the above code prettier? One thing I can see is that
if it's possible to return from inside a generator, it can be more
straightforward to get the values out of the accumulator at the end:

    try:
        while 1:
            value = yield
            total += value
            length += 1
            print(length, value, total)
    except GeneratorExit:
        return total, length

With Guido's proposed "for item from yield" syntax, IIUC this can be
prettied up even more as:

     for value from yield:
            total += value
            length += 1
    return total, length

Are there other benefits to the proposed extensions? How will the call
sites be improved? I'm not sure how I would rewrite main() to be
prettier/more clear in light of the proposals…

Thanks,

-- Carl Johnson



More information about the Python-ideas mailing list