[Chris Angelico <ros...@gmail.com>]
> I'm concerned that there are, in effect, two quite different uses of
> the exact same syntax.

Yes, the construct implements a profoundly different meaning of
"scope" depending on the context it appears in.

> 1) In an arbitrary expression, local() creates a scope that is defined
> entirely by the parentheses.

Yes.

> 2) In an 'if' header, the exact same local() call creates a scope that
> extends to the corresponding suite.

And in a 'while' header, and also possibly (likely) including
associated suites (elif/else).

So it goes ;-)  There is nothing "obvious" you can say inside an "if"
or "while" expression that says "and, oh ya, this name also shadows
anything of the same name from here on, except when it stops doing
so".

Even in C, e.g., it's not *obvious* what the scope of `i` is in:

    for (int i = 0; ...) {
    }

It needs to be learned.  Indeed, it's so non-obvious that C and C++
give different answers.  The {...} part by itself introduces a new
scope in both languages.  In C++ the `int i` is viewed as being _part_
of that scope, despite that it's outside the braces.  But in C the
`int i` is really viewed as being part of a Yet Another new scope
_enclosing_ the scope introduced by {...}, but nevertheless ending
when the {...} scope ends.

Not that it matters much.  The practical effect is that, e.g.,

    double i = 3.0;

is legal as the first line of the block in C (shadows the `int i`),
but illegal in C++ (a conflicting declaration for `i` in a single
scope).

In either case, it's only "obvious" if you learned it and then stopped
thinking too much about it ;-)


> For instance:
>
> a = 1; b = 2
> x = a + local(a = 3, b = 4, a + b) + b
> if x == 10:
>     # Prints "x is 10: 1 2"
>     print("x is 10: ", a, b)
>
> This makes reasonable sense. The parentheses completely enclose the
> local scope. It's compiler magic, and you cannot explain it as a
> function call, but it makes intuitive sense.

Yup, it's effectively a function-like spelling of any number of
binding constructs widely used in functional languages.  I had mostly
in mind Haskell's

    "let" pile-of-bindings "in" expression

spelled as

    "local(" pile-of-bindings "," expression ")"

The points to using function-call-like syntax were already covered
("nothing syntactically new to learn there", since the syntax for
specifying keyword arguments is already understood, and already groups
as intended).


> But the same thing inside the if header itself would be much weirder.
> I'm actually not even sure what it would do.

You think I am? ;-)  I don't know that it matters, because intended
use cases are far simpler than all the goofy things people _can_ dream
up just for the hell of it.  They need to be defined, but exactly how
isn't of much interest to me.

For example, let's put your example in an `if`:

    a = 1; b = 2
    if a + local(a = 3, b = 4, a + b) + b:

The rules I sketched pretty clearly imply that would be evaluated as:

    if 1 + (3+4) + 4:

It's the final "4" that's of interest.  In your original example the
original `b` was restored because ")" ended the new scope, leaving the
final "+b" to resolve to "+2".  But because it's in an "if" expression
here, the new scope doesn't end at ")" anymore.


> And you've clearly shown that the local() call can
> be anywhere inside the condition, based on these examples:

And/or used multiple times, and/or used in nested ways.  None of which
anyone will actually do ;-)


>> ...
>> if local(m = re.match(regexp, line)) is not None:
>>     print(m.group(0))
>
> At what point does the name 'm' stop referring to the local? More generally:

Probably at the end of the final (if any) `elif` or `else` suite
associated with the `if`/`while`, but possibly at the end of the suite
associated with the `if`/`while`.

Time to note another subtlety:  people don't _really_ want "a new
scope" in Python.  If they did, then _every_ name appearing in a
binding context (assignment statement target, `for` target, ...) for
the duration would vanish when the new scope ended.  What they really
want is a new scope with an implied "nonlocal" declaration for every
name appearing in a binding context _except_ for the specific names
they're effectively trying to declare as being "sublocal" instead.

So It's somewhat of a conceptual mess no mater how it's spelled ;)  In
most other languages this doesn't come up because the existence of a
variable in a scope is established by an explicit declaration rather
than inferred from examining binding sites.


>
> if local(m = ...) is not m:
>     print("Will I ever happen?")

No, that `print` can't be reached.


> Perhaps it would be better to make this special case *extremely*
> special. For instance:
>
> if_local: 'if' 'local' '(' local_item (',' local_item)* ')' ':' suite
>
> as the ONLY way to have the local names persist. In other words, if
> you tack "is not None" onto the outside of the local() call, it
> becomes a regular expression-local, and its names die at the close
> parentheses. It'd still be a special case, but it'd be a bit saner to
> try to think about.

That's an interesting twist ... but, to me, if "local()" inside an
if/while expression _can_ be deeply magical, then I'd be less
surprised over time it it were _always_ deeply magical in those
contexts.

I could change my mind if use cases derived from real code suggest it
would be a real problem.

Or if there's no real-life interest in catering to sublocal scopes in
expressions anyway ... then there's no reason to even try to use
something that makes clean sense for an expression.
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to