Peter O'Connor wrote:
> Alright hear me out here:
> 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.
> Then I learned about the beautiful and terrible "for x in [value]":
> def running_average(x_seq):
>     return [avg for avg in [0] for t, x in enumerate(x_seq) for avg in
> [avg*t/(t+1) + x/(t+1)]]
> Many people find this objectionable because it looks like there are 3 for
> loops, but really there's only one: loops 0 and 2 are actually assignments.

You can solve this via `itertools.accumulate` in a concise and clear way:

    [x/n for n, x in enumerate(it.accumulate(x_seq), 1)]

> My Proposal
> What if we just officially bless this "using for as a temporary assignment"
> arrangement, and allow "for x=value" to mean "assign within the scope of
> this for".  It would be identical to "for x in [value]", just more
> readable.  The running average function would then be:
> def running_average(x_seq):
>     return [avg for avg=0 for t, x in enumerate(x_seq) for avg = avg *
> t/(t+1) + x / (t+1)]
> ------ P.S. 1
> I am aware of Python 3.8's new "walrus" operator, which would make it:
> def running_average(x_seq):
>     avg = 0
>     return [avg := avg*t/(t+1) + x / (t+1) for t, x in enumerate(x_seq)]
> But it seems ugly and bug-prone to be initializing a in-comprehension
> variable OUTSIDE the comprehension.
> ------ P.S. 2
> The "for x = value" syntax can achieve things that are not nicely
> achievable using the := walrus.  Consider the following example (wherein we
> carry forward a "hidden" variable h but do not return it):
> y_seq = [y for h=0 for x in x_seq for y, h = update(x, h)]
> There's not really a nice way to do this with the walrus because you can't
> (as far as I understand) combine it with tuple-unpacking.  You'd have to do
> something awkward like:
> yh = None, 0
> y_seq, _ = zip(*(yh := update(x, yh[1]) for x in x_seq))

You can't use `:=` with tuple unpacking but you can use it with tuples 
directly; this requires a definition of the initial tuple (preferably outside 
the loop) but this is i.m.o. a plus since it clearly marks the initial 
conditions for your algorithm:

    yh = (None, 2)  # initial values
    [(yh := update(x, yh[1]))[0] for x in x_seq]

If you really ever have the need to carry a variable over into a comprehension 
(which might be a valid thing, for example in a class body) then you can still 
resort to an additional `for` loop (as you've already indicated); after all 
it's not too bad and you can even put the different loops on different lines + 
add a comment if necessary:

    class Foo:
        a = 1
        # this won't work:
        b = [x*a for x in range(5)]
        # we can carry `a` over to the comprehension as follows:
        b = [x*a for a in [a] for x in range(5)]

Using just `for = ...` is not much of a difference, especially since most 
people will see the `for` and immediately assume it's a loop (so in that sense 
it's even more confusing).
_______________________________________________
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/XKMPOMS3QSUURFCI5YDEAPJRGCCQAXBI/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to