[Python-ideas] Proposal: A Reduce-Map Comprehension and a "last" builtin

Peter O'Connor peter.ed.oconnor at gmail.com
Wed Apr 11 10:37:09 EDT 2018


>
> It's worth adding a reminder here that "having more options on the
> market" is pretty directly in contradiction to the Zen of Python -
> "There should be one-- and preferably only one --obvious way to do
> it".


I've got to start minding my words more.  By "options on the market" I more
meant it in a "candidates for the job" sense.  As in in the end we'd select
just one, which would in retrospect or if Dutch would seem like the obvious
choice.  Not that "everyone who uses Python should have more ways to do
this".

My reason for starting this is that there isn't "one obvious way" to do
this type of operation now (as the diversity of the exponential-moving-average
"zoo"
<https://github.com/petered/peters_example_code/blob/master/peters_example_code/ways_to_skin_a_cat.py>
attests)

------

Let's look at a task where there is "one obvious way"

Suppose someone asks: "How can I build a list of squares of the first 100
odd numbers [1, 9, 25, 49, ....] in Python?"  The answer is now obvious -
few people would do this:

    list_of_odd_squares = []
    for i in range(100):
        list_of_odd_squares.append((i*2+1)**2)

or this:

    def iter_odd_squares(n)):
        for i in range(n):
            yield (i*2+1)**2

    list_of_odd_squares = list(iter_odd_squares(100))

Because it's just more clean, compact, readable and "obvious" to do:

    list_of_even_squares = [(i*2+1)**2 for i in range(100)]

Maybe I'm being presumptuous, but I think most Python users would agree.

-------

Now lets switch our task computing the exponential moving average of a
list.  This is a stand-in for a HUGE range of tasks that involve carrying
some state-variable forward while producing values.

Some would do this:

    smooth_signal = []
    average = 0
    for x in signal:
        average = (1-decay)*average + decay*x
        smooth_signal.append(average)

Some would do this:

    def moving_average(signal, decay, initial=0):
        average = initial
        for x in signal:
            average = (1-decay)*average + decay*x
            yield average

    smooth_signal = list(moving_average(signal, decay=decay))

Lovers of one-liners like Serhiy would do this:

    smooth_signal = [average for average in [0] for x in signal for average
in [(1-decay)*average + decay*x]]

Some would scoff at the cryptic one-liner and do this:

    def update_moving_average(avg, x, decay):
        return (1-decay)*avg + decay*x

    smooth_signal = list(itertools.accumulate(itertools.chain([0], signal),
func=functools.partial(update_moving_average, decay=decay)))

And others would scoff at that and make make a class, or use coroutines.

------

There've been many suggestions in this thread (all documented here:
https://github.com/petered/peters_example_code/blob/master/peters_example_code/ways_to_skin_a_cat.py)
and that's good, but it seems clear that people do not agree on an
"obvious" way to do things.

I claim that if

    smooth_signal = [average := (1-decay)*average + decay*x for x in signal
from average=0.]

Were allowed, it would become the "obvious" way.

Chris Angelico's suggestions are close to this and have the benefit of
requiring no new syntax in a PEP 572 world :

    smooth_signal = [(average := (1-decay)*average + decay*x) for average
in [0] for x in signal]
or
    smooth_signal = [(average := (1-decay)*(average or 0) + decay*x) for x
in signal]
or
   average = 0
   smooth_signal = [(average := (1-decay)*average + decay*x) for x in
signal]

But they all have oddities that detract from their "obviousness" and the
oddities stem from there not being a built-in way to initialize.  In the
first, there is the odd "for average in [0]" initializer..   The second
relies on a hidden "average = None" which is not obvious at all, and the
third has the problem that the initial value is bound to the defining scope
instead of belonging to the generator.  All seem to have oddly redundant
brackets whose purpose is not obvious, but maybe there's a good reason for
that.

If people are happy with these solutions and still see no need for the
initialization syntax, we can stop this, but as I see it there is a "hole"
in the language that needs to be filled.

On Wed, Apr 11, 2018 at 3:55 AM, Paul Moore <p.f.moore at gmail.com> wrote:

> On 11 April 2018 at 04:41, Steven D'Aprano <steve at pearwood.info> wrote:
> >> > But in a way that more intuitively expresses the intent of the code,
> it
> >> > would be great to have more options on the market.
> >>
> >> It's worth adding a reminder here that "having more options on the
> >> market" is pretty directly in contradiction to the Zen of Python -
> >> "There should be one-- and preferably only one --obvious way to do
> >> it".
> >
> > I'm afraid I'm going to (mildly) object here. At least you didn't
> > misquote the Zen as "Only One Way To Do It" :-)
> >
> > The Zen here is not a prohibition against there being multiple ways to
> > do something -- how could it, given that Python is a general purpose
> > programming language there is always going to be multiple ways to write
> > any piece of code? Rather, it exhorts us to make sure that there are one
> > or more ways to "do it", at least one of which is obvious.
>
> I apologise if I came across as implying that I thought the Zen said
> that having multiple ways was prohibited. I don't (and certainly the
> Zen doesn't mean that). Rather, I was saying that using "it gives us
> an additional way to do something" is a bad argument in favour of a
> proposal for Python. At a minimum, the proposal needs to argue why the
> new feature is "more obvious" than the existing ways (bonus points if
> the proposer is Dutch - see the following Zen item ;-)), or why it
> offers a capability that isn't possible with the existing language.
> And I'm not even saying that the OP hasn't attempted to make such
> arguments (even if I disagree with them). All I was pointing out was
> that the comment "it would be great to have more options on the
> market" implies a misunderstanding of the design goals of Python
> (hence my "reminder" of the principle I think is relevant here).
>
> Sorry again if that's not what it sounded like.
> Paul
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180411/f20b1235/attachment-0001.html>


More information about the Python-ideas mailing list