On Aug 9, 2019, at 08:47, Peter O'Connor <peter.ed.ocon...@gmail.com> wrote:
> 
> I've often found that it would be useful for the following type of expression 
> to be condensed to a one-liner: 
> 
> def running_average(x_seq): 
>     averages = []
>     avg = 0
>     for t, x in enumerate(x_seq): 
>         avg =  avg*t/(t+1) + x/(t+1)
>         averages.append(avg)
>     return averages
> 
> Because really, there's only one line doing the heavy lifting here, the rest 
> is kind of boilerplate.

It seems like the only reason you can’t write this with accumulate is that 
accumulate doesn’t take a start value like reduce does?

And I think this would be a lot clearer and more readable, especially if you’re 
doing this kind of thing more than once:

    def running(xs, func, start):
        yield from accumulate(enumerate(xs), lambda avg, told: func(avg, *tx), 
start)

    def running_average(xs, func):
        yield from running(xs, lambda avg, t, x: avg*t/(t+1) + x/(t+1), 0.0)

Now the part that does the heavy lifting is all in one place and just does what 
it’s says, without being confusingly interleaved with the boilerplate. Plus, 
the parts of the boilerplate that are reusable are abstracted into functions 
(accumulate and running) that can be reused, while the rest of it has vanished.

(This is one of those rare cases where 2.x-style decomposing def/lambda was 
actually useful, but if that extra lambda in running really bothers you, that’s 
another one-liner HOF you can abstract out trivially and reuse.)

More generally, it’s a lot easier to use comprehensions and higher order 
functions if your algorithm can be written in terms of “generate the next 
immutable value” instead of “update the mutable variable”, and I don’t think 
that’s a limitation of the language. Comprehensions are much more readable when 
they’re declarative than when they’re for statements in disguise. 

Also, I don’t think the reason people were objecting to your four-clause 
comprehension was that it wasn’t easy enough to tell that the innermost clause 
only “loops” exactly one time, but that it’s a comprehension with four clauses 
in the first place. Changing the spelling of that clause to make the 
no-actual-looping doesn’t solve that.

Finally, you can already play tricks with the walrus operator to avoid moving 
things like initialization outside the comprehension, just as you could with 
your proposed syntax. For example, “for t, x in (avg:=0) or enumerate(xs)” is a 
perfectly valid clause that assigns 0 to avg and then loops over the enumerate, 
and it doesn’t require you to turn one for clause into two. But it still adds 
just as much complexity for the reader to deal with, so I think you’re still 
better off not doing it.

(As a side note, you probably want a numerical stable average like the ones in 
statistics or numpy, rather than one that accumulates float rounding errors 
indiscriminately, but that’s another issue.)
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/3RPVRDFALMRMYHWYLCP4EPTOI4LKQIZY/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to