On 17 February 2018 at 02:31, Ethan Furman <et...@stoneleaf.us> wrote: > On 02/15/2018 11:55 PM, Nick Coghlan wrote: >> However, while I think that looks nicer in general, we'd still have to >> choose between two surprising behaviours: >> >> * implicitly delete the statement locals after the statement where >> they're set (which still overwrites any values previously bound to >> those names, similar to what happens with exception clauses) > > > If we're overwriting locals anyway, don't delete it. The good reason for > unsetting an exception variable doesn't apply here. > >> * skip deleting, which means references to subexpressions may last >> longer than expected (and we'd have the problem where embedded >> assignments could overwrite existing local variables) > > Odds are good that we'll want/need that assignment even after the immediate > expression it's used in. Let it stick around.
If we want to use a subexpression in multiple statements, then regular assignment statements already work fine - breaking out a separate variable assignment only feels like an inconvenience when we use a subexpression twice in a single statement, and then don't need it any further. By contrast, if we have an implicit del immediately after the statement for any statement local variables, then naming a subexpression only extends its life to the end of the statement, not to the end of the current function, and it's semantically explicit that you *can't* use statement locals to name subexpressions that *aren't* statement local. The other concern I have with any form of statement local variables that can overwrite regular locals is that we'd be reintroducing the problem that comprehensions have in Python 2.x: unexpectedly rebinding things in non-obvious ways. At least with an implicit "del" the error would be more readily apparent, and if we disallow closing over statement local variables (which would be reasonable, since closures aren't statement local either), then we can avoid interfering with regular locals without needing to introduce a new execution scope. So let's consider a spec for statement local variable semantics that looks like this: 1. Statement local variables do *not* appear in locals() 2. Statement local variables are *not* visible in nested scopes (of any kind) 3. Statement local variables in compound statement headers are visible in the body of that compound statement 4. Due to 3, statement local variable references will be syntactically distinct from regular local variable references 5. Nested uses of the same statement local variable name will shadow outer uses, rather than overwriting them The most discreet syntactic marker we have available is a single leading dot, which would allow the following (note that in the simple assignment cases, breaking out a preceding assignment would be easy, but the perk of the statement local spelling is that it works in *any* expression context): value = .s.strip()[4:].upper() if (var1 as .s) is not None else None value = .s[4:].upper() if (var1 as .s) is not None else None value = .v if (var1 as .v) is not None else .v if (var2 as .v) is not None else var3 value = .v if not math.isnan((var1 as .v)) else tmp if not math.isnan((var2 as .v)) else var3 value = .f() if (calculate as .f) is not None else default filtered_values = [.v for x in keys if (get_value(x) as .v) is not None] range((calculate_start() as .start), .start+10) data[(calculate_start() as .start):.start+10] value if (lower_bound() as .min_val) <= value < .min_val+tolerance else 0 print(f"{(get_value() as .v)!r} is printed in pure ASCII as {.v!a} and in Unicode as {.v}") if (pattern.search(data) as .m) is not None: # .m is available here as the match result else: # .m is also available here (but will always be None given the condition) # .m is no longer available here Except clauses would be updated to allow the "except ExceptionType as .exc" spelling, which would give full statement local semantics (i.e. disallow closing over the variable, hide it from locals), rather than just deleting it at the end of the clause execution. Similarly, with statements would allow "with cm as .enter_result" to request statement local semantics for the enter result. (One potential concern here would be the not-immediately-obvious semantic difference between "with (cm as .the_cm):" and "with cm as .enter_result:"). To make that work at an implementation level we'd then need to track the following in the compiler: * the current nested statement level in the current compilation (so we can generate distinct names at each level) * a per-statement set of local variable names (so we can clear them at the end of the statement) * the largest number of concurrently set statement local variables (so we can allocate space for them in the frame) * the storage offset to use for each statement local variable and then frames would need an additional storage area for statement locals, as well as new opcodes for accessing them. Adding yet more complexity to an already complicated scoping model is an inherently dubious proposal, but at the same time, it does provide a way to express "and" and "or" semantics in terms of statement local variables and conditional expressions, and comparison chaining in terms of statement local variables and the "and" operator (so conceptually this kind of primitive does already exist in the language, just only as an operator-specific special case inside the interpreter). Cheers, Nick. -- Nick Coghlan | ncogh...@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/