On 27.06.2018 16:49, Steven D'Aprano wrote:
On Wed, Jun 27, 2018 at 08:00:20AM -0400, Eric V. Smith wrote:
On 6/27/2018 7:08 AM, Chris Angelico wrote:
It gets funnier with nested loops. Or scarier. I've lost the ability
to distinguish those two.

def test():
     spam = 1
     ham = 2
     vars = [key1+key2 for key1 in locals() for key2 in locals()]
     return vars

Wanna guess what that's gonna return?
I'm not singling out Chris here, but these discussions would be easier
to follow and more illuminating if the answers to such puzzles were
presented when they're posed.
You can just copy and paste the function into the interactive
interpreter and run it :-)

But where's the fun in that? The point of the exercise is to learn first
hand just how complicated it is to try to predict the *current* scope
behaviour of comprehensions. Without the ability to perform assignment
inside them, aside from the loop variable, we've managed to avoid
thinking too much about this until now.

It also demonstrates the unrealisticness of treating comprehensions as a
separate scope -- they're hybrid scope, with parts of the comprehension
running in the surrounding local scope, and parts running in an sublocal
scope.

Earlier in this thread, Nick tried to justify the idea that
comprehensions run in their own scope, no matter how people think of
them -- but that's an over-simplification, as Chris' example above
shows. Parts of the comprehension do in fact behave exactly as the naive
model would suggest (even if Nick is right that other parts don't).

As complicated and hairy as the above example is, (1) it is a pretty
weird thing to do, so most of us will almost never need to consider it;
and (2) backwards compatibility requires that we live with it now (at
least unless we introduce a __future__ import).

If we can't simplify the scope of comprehensions, we can at least
simplify the parts that actually matters. What matters are the loop
variables (already guaranteed to be sublocal and not "leak" out of the
comprehension) and the behaviour of assignment expressions (open to
discussion).

Broadly speaking, there are two positions we can take:

1. Let the current implementation of comprehensions as an implicit
hidden function drive the functionality; that means we duplicate the
hairiness of the locals() behaviour seen above, although it won't be
obvious at first glance.

What this means in practice is that assignments will go to different
scopes depending on *where* they are in the comprehension:

     [ expr   for x in iter1  for y in iter2  if cond   ...]
     [ BBBBBB for x in AAAAAA for y in BBBBBB if BBBBBB ...]

Assignments in the section marked "AAAAAA" will be in the local scope;
assignments in the BBBBBB sections will be in the sublocal scope. That's
not too bad, up to the point you try to assign to the same name in
AAAAAA and BBBBBB. And then you are likely to get confusing hard to
debug UnboundLocalErrors.

This isn't as messy as you make it sound if you remember that the outermost iterable is evaluated only once at the start and all the others -- each iteration.
Anyone using comprehensions has to know this fact.
The very readable syntax also makes it rather straightforward (though admittedly requiring some hand-tracing) to figure out what is evaluated after what.


2. Or we can keep the current behaviour for locals and the loop
variables, but we can keep assignment expressions simple by ensuring
they always bind to the enclosing scope. Compared to the complexity of
the above, we have the relatively straight forward:

     [ AAAAAA for x in AAAAAA for y in AAAAAA if AAAAAA ...]

The loop variables continue to be hidden away in the invisible, implicit
comprehension function, where they can't leak out, while explicit
assignments to variables (using := or given or however it is spelled)
will always go into the surrounding local scope, like they do in every
other expression.

Does it matter that the implementation of this requires an implicit
nonlocal declaration for each assignment? No more than it matters that
comprehensions themselves require an implicit function.

And what we get out of this is simpler semantics at the Python level:

- Unless previous declared global, assignment expressions always bind to
the current scope, even if they're inside a comprehension;

- and we don't have to deal with the oddity that different bits of a
comprehension run in different scopes (unless we go out of our way to
use locals()); merely using assignment expressions will just work
consistently and simply, and loop variables will still be confined to
the comprehension as they are now.



--
Regards,
Ivan

_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to