[Chris Angelico
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.