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

Steven D'Aprano steve at pearwood.info
Thu Apr 5 21:58:55 EDT 2018


On Fri, Apr 06, 2018 at 11:02:30AM +1000, Chris Angelico wrote:
> On Fri, Apr 6, 2018 at 10:37 AM, Steven D'Aprano <steve at pearwood.info> wrote:

[...]
> > All we need now is a way to feed in the initial value for average. And
> > that could be as trival as assigning a local name for it:
> >
> >     average = 0
> >
> > before running the comprehension.
> 
> That would only work if the comprehension is executed in the same
> context as the surrounding code, instead of (as currently) being in a
> nested function. Otherwise, there'd need to be an initializer inside
> the comprehension - but that can be done (although it won't be
> particularly beautiful).

Not necessarily: we could keep the rule that comprehensions are executed 
in their own scope. We just add the rule that if a name is used as a 
sublocal name binding, then (and only then) it is initialised from the 
surrounding scopes. If there is no such surrounding name, then the 
sublocal remains uninitialised and trying to evaluate it will give 
UnboundLocalError.

That's similar to how Lua works with locals/globals, and yes, I'm aware 
of the irony that I'm proposing this. I don't like the way it works in 
Lua where it applies *everywhere*, but I think it is justifiable and 
useful if applied specifically to comprehensions.

A contrived example: suppose we want the running sum of a list, written 
as a list comprehension. This runs, but doesn't do what we want:

    [((x as spam) + spam) for x in [1, 2, 3]]
    => returns [2, 4, 6]

This version fails as we try to evaluate spam before it is defined:

    [(spam + (x as spam)) for x in [1, 2, 3]]

But if spam was copied from the surrounding scope, this would work:

    spam = 0
    [(spam + (x as spam)) for x in [1, 2, 3]]
    => returns [1, 3, 5]


and of course this would allow Peter's reduce/map without the ugly and 
ackward "from spam=0" initialiser syntax. (Sorry Peter.)


If you don't like that implicit copying, let's make it explicit:

    spam = 0
    [(spam + (x as nonlocal spam)) for x in [1, 2, 3]]

(Should we allow global spam as well? Works for me.)

Or if you prefer the Pascal-style assignment syntax that Guido favours:

    [(spam + (nonlocal spam := x)) for x in [1, 2, 3]]



-- 
Steve


More information about the Python-ideas mailing list