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" 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 wrote:
On 11 April 2018 at 04:41, Steven D'Aprano <steve@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@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/