On 24 March 2018 at 14:41, Steven D'Aprano <steve@pearwood.info> wrote:
On Sat, Mar 24, 2018 at 05:09:54AM +1100, Chris Angelico wrote:
> >> Just as function-local names shadow global names for the scope of the
> >> function, statement-local names shadow other names for that statement.
> >> (They can technically also shadow each other, though actually doing this
> >> should not be encouraged.)
> >
> > That seems weird.
>
> Which part? That they shadow, or that they can shadow each other?

Shadowing themselves.

I'm still not convinced these should just shadow local variables. Of
course locals will shadow nonlocals, which shadow globals, which shadow
builtins. I'm just not sure that we gain much (enough?) to justify
adding a new scope between what we already have:

proposed statement-local
local
nonlocal
class (only during class statement)
global
builtins

I think that needs justification by more than just "it makes the
implementation easier".

Introducing the new scoping behaviour doesn't make the implementation easier, it makes it harder. However, there are specific aspects of how that proposed new scope works (like not being visible from nested scopes) that make the implementation easier, since they eliminate a whole swathe of otherwise complicated semantic questions :)

At a user experience level, the aim of the scoping limitation is essentially to help improve "code snippet portability".

Consider the following piece of code:

    squares = [x**2 for x in iterable]

In Python 2.x, you not only have to check whether or not you're already using "squares" for something, you also need to check whether or not you're using "x", since the iteration variable leaks.

In Python 3.x, you only need to check for "squares" usage, since the comprehension has its own inner scope, and any "x" binding you may have defined will be shadowed instead of being overwritten.

For PEP 572, the most directly comparable example is code like this:

    # Any previous binding of "m" is lost completely on the next line
    m = re.match(...)
    if m:
        print(m.groups(0))

In order to re-use that snippet, you need to double-check the surrounding code and make sure that you're not overwriting an "m" variable already used somewhere else in the current scope.

With PEP 572, you don't even need to look, since visibility of the "m" in the following snippet is automatically limited to the statement itself:

    if (re.match(...) as m):
        print(m.groups(0))
    # Any previous binding of "m" is visible again here, and hence a common source of bugs is avoided :)

Cheers,
Nick.

--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia