I hope nobody will mind too much if I throw in my (relatively uninformed) 2c before some of the big guns respond.
First: Well done, Chris, for all the work on this. IMHO this
could be a useful Python enhancement (and reduce the newsgroup
churn :-)).
It may be pedantic of me (and it will produce a more pedantic-sounding sentence) but I honestly think thatThis is a suggestion that comes up periodically here or on python-dev. This proposal introduces a way to bind a temporary name to the value of an expression, which can then be used elsewhere in the current statement. The nicely-rendered version will be visible here shortly: https://www.python.org/dev/peps/pep-0572/ ChrisA PEP: 572 Title: Syntax for Statement-Local Name Bindings Author: Chris Angelico <rosuav@gmail.com> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 28-Feb-2018 Python-Version: 3.8 Post-History: 28-Feb-2018 Abstract ======== Programming is all about reusing code rather than duplicating it. When an expression needs to be used twice in quick succession but never again, it is convenient to assign it to a temporary name with very small scope. By permitting name bindings to exist within a single statement only, we make this both convenient and safe against collisions.
IMHO the first sentence is a bit of an overstatement (though of course it's a big part of the PEP's "sell").Rationale ========= When an expression is used multiple times in a list comprehension, there are currently several suboptimal ways to spell this, and no truly good ways. A statement-local name allows any expression to be temporarily captured and then used multiple times.
I agree, pro tem (not that I am claiming that my opinion counts for much). I'm personally somewhat allergic to making parentheses mandatory where they really don't need to be, but trying to think about where they could be unambiguously omitted makes my head spin. At least, if we run with this for now, then making them non-mandatory in some contexts, at some future time, won't lead to backwards incompatibility.Syntax and semantics ==================== In any context where arbitrary Python expressions can be used, a named expression can appear. This must be parenthesized for clarity,
# Calling the function twice (assuming that side effects can be ignored)and is of the form `(expr as NAME)` where `expr` is any valid Python expression, and `NAME` is a simple name. The value of such a named expression is the same as the incorporated expression, with the additional side-effect that NAME is bound to that value for the remainder of the current statement. 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 also shadow each other, though actually doing this should be strongly discouraged in style guides. Example usage ============= These list comprehensions are all approximately equivalent:: # Calling the function twice
# External helper functionstuff = [[f(x), f(x)] for x in range(5)] # Helper function
Please feel free to ignore this, but (trying to improve on the above example):def pair(value): return [value, value] stuff = [pair(f(x)) for x in range(5)] # Inline helper function stuff = [(lambda v: [v,v])(f(x)) for x in range(5)] # Extra 'for' loop - see also Serhiy's optimization stuff = [[y, y] for x in range(5) for y in [f(x)]] # Expanding the comprehension into a loop stuff = [] for x in range(5): y = f(x) stuff.append([y, y])
Maybe add to last sentence "and of adding (at least conceptually) extra steps: building a 1-element list, then extracting the first element"# Using a statement-local name stuff = [[(f(x) as y), y] for x in range(5)] If calling `f(x)` is expensive or has side effects, the clean operation of the list comprehension gets muddled. Using a short-duration name binding retains the simplicity; while the extra `for` loop does achieve this, it does so at the cost of dividing the expression visually, putting the named part at the end of the comprehension instead of the beginning.
IMHO this is not only acceptable, but the (only) correct behaviour. Your crystal-clear statement "the new name binding will shadow the other name from the point where it is evaluated until the end of the statement " is critical and IMO what should happen.Statement-local name bindings can be used in any context, but should be avoided where regular assignment can be used, just as `lambda` should be avoided when `def` is an option. Open questions ============== 1. What happens if the name has already been used? `(x, (1 as x), x)` Currently, prior usage functions as if the named expression did not exist (following the usual lookup rules); the new name binding will shadow the other name from the point where it is evaluated until the end of the statement. Is this acceptable? Should it raise a syntax error or warning?
It's great that it works perfectly in functions and list comprehensions, but it sounds as if, at top level, in rare circumstances it could produce a hard-to-track-down bug, which is not exactly desirable. It's hard to say more without knowing more details. As a stab in the dark, is it possible to avoid it by including the module name in the mangling? Sorry if I'm talking rubbish.a special (and mostly-invisible) name mangling. This works perfectly inside functions (including list comprehensions), but not at top level. Is this a serious limitation? Is it confusing?
IMHO this is an implementation detail. IMO you should have some idea what you're doing when you use locals(). But I think consistency matters - either the temporary variable *always* gets into locals() "from the point where it is evaluated until the end of the statement", or it *never* gets into locals(). (Possibly the language spec should specify one or the other - I'm not sure, time may tell.)3. The interaction with locals() is currently[1] slightly buggy. Should statement-local names appear in locals() while they are active (and shadow any other names from the same function), or should they simply not appear?
This (4. and 5.) shows that we are using "as" in more than one sense, and in a perfect world we would use different keywords. But IMHO (admittedly, without having thought about it much) this isn't much of a problem. Again, perhaps some clarifying examples would help.4. Syntactic confusion in `except` statements. While technically unambiguous, it is potentially confusing to humans. In Python 3.7, parenthesizing `except (Exception as e):` is illegal, and there is no reason to capture the exception type (as opposed to the exception instance, as is done by the regular syntax). Should this be made outright illegal, to prevent confusion? Can it be left to linters? 5. Similar confusion in `with` statements, with the difference that there is good reason to capture the result of an expression, and it is also very common for `__enter__` methods to return `self`. In many cases, `with expr as name:` will do the same thing as `with (expr as name):`, adding to the confusion.