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/