On Sun, Mar 25, 2018 at 4:34 PM, Guido van Rossum email@example.com wrote:
This is a super complex topic. There are at least three separate levels of critique possible, and all are important.
Thank you for your detailed post. I'll respond to some of it here, and some more generally below.
First there is the clarity of the PEP. Steven D'Aprano has given you great detailed feedback here and you should take it to heart (even if you disagree with his opinion about the specifics). I'd also recommend treating some of the "rejected alternatives" more like "open issues" (which are to be resolved during the review and feedback cycle). And you probably need some new terminology -- the abbreviation SNLB is awkward (I keep having to look it up), and I think we need a short, crisp name for the new variable type.
Agreed that it needs a new name. I've been trying to avoid looking for something that's short-yet-inaccurate, and sticking to the accurate-but-unwieldy; perhaps Nick's "sublocal" will serve the purpose?
Then there is the issue of syntax. While `(f() as x)` is a cool idea (and we should try to recover who deserves credit for first proposing it), it's easy to overlook in the middle of an exception. It's arguably more confusing because the scoping rules you propose are so different from the existing three other uses of `as NAME` -- and it causes an ugly wart in the PEP because two of those other uses are syntactically so close that you propose to ban SNLBs there. When it comes to alternatives, I think we've brainwashed ourselves into believing that inline assignments using `=` are evil that it's hard to objectively explain why it's bad -- we're just repeating the mantra here. I wish we could do more quantitative research into how bad this actually is in languages that do have it. We should also keep an open mind about alternative solutions present in other languages. Here it would be nice if we had some qualitative research into what other languages actually do (both about syntax and about semantics, for sure).
The third issue is that of semantics. I actually see two issues here. One is whether we need a new scope (and whether it should be as weird as proposed). Steven seems to think we don't. I'm not sure that the counter-argument that we're already down that path with comprehension scopes is strong enough. The other issue is that, if we decide we *do* need (or want) statement-local scopes, the PEP must specify the exact scope of a name bound at any point in a statement. E.g. is `d[x] = (f() as x)` valid?
Yes, it is. The sublocal name (I'm going to give this term a try and see how it works; if not, we can revert to "bullymong", err I mean "SLNB") remains valid for all retrievals until the end of the statement, which includes the assignment.
And what should we do if a name may or may not be bound, as in `if (f(1) as x) or (f(2) as y): g(y)` -- should that be a compile-time error (since we can easily tell that y isn't always defined when `g(y)` is called) or a runtime error (as we do for unbound "classic" locals)?
The way I've been thinking about it (and this is reflected in the reference implementation) is that 'y' becomes, in effect, a new variable that doesn't collide with any other 'y' in the same function or module or anything. For the duration of this statement, 'x' and 'y' are those special variables. So it's similar to writing this:
def func(): x = f(1) if x: g(y) else: y = f(2) g(y)
which will raise UnboundLocalError when x is true. The same behaviour happens here.
And there are further details, e.g. are these really not allowed to be closures? And are they single-assignment? (Or can you do e.g. `(f(1) as x) + (f(2) as x)`?)
Technically, what happens is that the second one creates _another_ sublocal name, whose scope begins from the point of assignment and goes to the end of the statement. Since this expression must all be within one statement, both sublocals will expire simultaneously, so it's effectively the same as reassigning to the same name, except that the old object won't be dereferenced until the whole statement ends. (And since Python-the-language doesn't guarantee anything about dereferenced object destruction timings, this will just be a point of curiosity.)
So, there are lots of interesting questions! I do think there are somewhat compelling use cases; more than comprehensions (which I think are already over-used) I find myself frequently wishing for a better way to write
m = pat.match(text) if m: g = m.group(0) if check(g): # Some check that's not easily expressed as a regex print(g)
It would be nice if I could write that as
if (m = pat.match(text)) and check((g = m.group(0))): print(g)
if (pat.match(text) as m) and check((m.group(0) as g)): print(g)
Agreed. I'm currently thinking that I need to do what several people have suggested and break this into two completely separate PEPs:
1) Sublocal namespacing 2) Assignment expressions
Sublocal names can be used in a number of ways. There could be a "with sublocal EXPR as NAME:" syntax that actually disposes of the name binding at the end of the block, and "except Exception as e:" could shadow rather than unbinding. Maybe list comprehensions could change, too - instead of creating a function, they just create a sublocal scope.
That may be the best way forward. I'm not sure.