[Python-ideas] Inline assignments using "given" clauses

Steven D'Aprano steve at pearwood.info
Sat May 12 03:37:28 EDT 2018

On Sat, May 12, 2018 at 02:37:20AM +0100, Rob Cliffe via Python-ideas wrote:

> Yeah, well, I'm totally lost.  Even after trying out this code, and 
> refactoring it once if not twice, I didn't understand it.  I don't know 
> what point you're trying to prove, but you seem to have comprehensively 
> proved it.

Do you mean Serhiy's example of currently supported syntax?

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

It helps if you know the algorithm for exponential smoothing: for 
each value x (aside from the first), the average is equal to a mix 
of the current value x and the previous average A, split by some 
proportion P:

    A = (1-P)*A + P*x

If P is 0.5, that is equivalent to taking the ordinary average between 
the current value and the previous average:

    A = (A+x)/2  # when P == 0.5

In the comprehension, P is called "decay" and A is called "average":

    average = (1-decay)*average + decay*x

Writing the comprehension as a single line is hard to read. Let's give 
it some structure:

smooth_signal = [average # append average to the results
                 for average in [0]
                     for x in signal 
                         for average in [(1-decay)*average + decay*x]

Horrible as it is, it is perfectly legal Python right now. It uses

    for name in SINGLE_ITEM_LIST

to perform an assignment. So that's equivalent to:

    average = 0
    for x in signal
        average = (1-decay)*average + decay*x
        append average to the results

Pull the initial value of average out of the comprehension, and use the 
PEP 572 syntax:

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

which is a huge improvement in my opinion. It would be more obvious if 
the expression being calculated came first:

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

but there are good reasons why the "as" syntax won't work. So it looks 
like we're stuck with needing to look ahead past the := to see the 
actual value being appended to the list.

A minor inconvenience, equivalent to that in ternary if, where we have 
to look ahead to see the condition:

    [target := COMPREHENSION_VALUE for x in sequence]

    true_value if CONDITION else false_value

So I expect that it will take me a little while to learn to look ahead 
and read binding-expressions fluently. (Like comprehensions themselves, 
really. It took me a few months to stop needing to pull them apart to 
understand them.)

He's Nick's version, as best as I am able to tell:

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

So we have the same look-ahead needed to see the expression we care 
about, but instead of merely having two characters := needed to do the 
binding, we need "given average =".


More information about the Python-ideas mailing list