[Python-Dev] The new and improved PEP 572, same great taste with 75% less complexity!

Steven D'Aprano steve at pearwood.info
Wed Apr 25 07:37:35 EDT 2018


On Tue, Apr 24, 2018 at 11:55:16PM -0700, Nathaniel Smith wrote:

> These examples all make me very nervous. The order of execution in
> comprehensions is pretty confusing to start with (right to left,
> except when it's left to right!).

I don't think comprehensions are ever right to left. With one small 
irregularity, execution follows Python's normal order, namely (mostly) 
left to right except where precedence requires something different, the 
if operator etc.

The one small irregularity is that the values produced by 
the comprehension are written first, rather than last. That is, given:

    [expression for item in iterable ...]

the initial expression doesn't get evaluated until the loop is entered. 
But the expression still evaluates in left-to-write order (modulo 
operator precedence etc) and everything following the first "for" 
keywords also evaluates in left-to-right order.


[...]
> Concretely, I find it unnerving that two of these work, and one doesn't:
> 
> # assignment on the right of usage
> results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

This shouldn't be surprising. We place the expression first because 
that's the most important part of the comprehension, the values it 
generates. Everything from the "for" onwards is just scaffolding needed 
to produce those values, its the leading expression that matters.

But clearly we can't expect to evaluate the expression *before* the 
loop, even though we want it written first. So although the 
comprehension is written expression first, the expression is actually 
evaluated *last*.

    results = [ for x in input_data 
                    if (y := f(x)) > 0 
                        (x, y, x/y)
              ]


So when reading a comprehension in execution order:

- look ahead to the first "for" keyword;

- read to the end of the comprehension, using normal left-to-right
  execution order (modulo the usual exceptions);

- jump back to the expression at the start;

- read in normal left-to-right execution order.

So aside from that small anomaly of the expression coming first, 
comprehensions use the same order as regular Python code. Binding 
expressions won't change that.


> I could probably figure it out if necessary, but even in
> simple cases like f(g(), h()), then do you really know for certain off
> the top of your head whether g() or h() runs first?

Of course. Left-to-right execution order (modulo the usual...) is a 
language guarantee.

> Does the average user?

I dare say the average user probably doesn't even think about it, and 
merely assumes that everything is left to right until they learn 
differently. In this case, that reasonable default position is correct. 
Everything is left to right until you learn differently.


> With code like f(a := g(), h(a)) this suddenly matters a lot!

That's hardly different from any other precedence issue, and even when 
precedence is specified by the language, sometimes adding a few extra 
brackets makes things clearer even when they're not needed:

    func(arg, (spam*n == token), (a := g()), h(a))


> But comprehensions suffer from a particularly extreme version of this,
> so it worries me that they're being put forward as the first set of
> motivating examples.

Ha, I think that comprehensions are one of the weaker motivating 
examples, but it was a discussion on Python-Ideas about adding special 
syntax *only* to comprehensions which lead to Chris writing the PEP. So 
it is purely an accident of history why comprehensions are the first 
example in the PEP.


> >     # Capturing regular expression match objects
> >     # See, for instance, Lib/pydoc.py, which uses a multiline spelling
> >     # of this effect
> >     if match := re.search(pat, text):
> >         print("Found:", match.group(0))
> 
> Now this is a genuinely compelling example! re match objects are
> always awkward to work with. But this feels like a very big hammer to
> make re.match easier to use :-). I wonder if there's anything more
> focused we could do here?

A reasonable point.


[...]
> However, I do think it'd be kinda confusing if we had:
> 
> if EXPR as X:
> while EXPR as X:
> with EXPR as X:
> 
> and the first two assign the value of EXPR to X, while the last one
> does something more subtle. Or maybe it'd be fine?

It would be fine, right up to the point that it wasn't, and then it 
would be a brain melting bug magnet that would cause programmers to 
curse us onto the 20th generation.



-- 
Steve


More information about the Python-Dev mailing list