On Tue, Feb 27, 2018 at 2:35 PM Chris Angelico <ros...@gmail.com> wrote:
> This 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 <ros...@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. > > > 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. > > > Syntax and semantics > ==================== > > In any context where arbitrary Python expressions can be used, a named > expression can appear. This must be parenthesized for clarity, 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 > stuff = [[f(x), f(x)] for x in range(5)] > > # Helper function > 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]) > > # 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. > > 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? > > 2. The current implementation [1] implements statement-local names using > 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? > > 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? > > 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. > > > References > ========== > > .. [1] Proof of concept / reference implementation > (https://github.com/Rosuav/cpython/tree/statement-local-variables) > > -1 today My first concern for this proposal is that it gives parenthesis a non-intuitive meaning. ()s used to be innocuous. A way to group things, make explicit the desired order of operations, and allow spanning across multiple lines. With this proposal, ()s occasionally take on a new meaning to cause additional action to happen _outside_ of the ()s - an expression length name binding. Does this parse? print(fetch_the_comfy_chair(cardinal) as chair, "==", chair) SyntaxError? My read of the proposal suggests that this would be required: print((fetch_the_comfy_chair(cardinal) as chair), "==", chair) But that sets off my "excess unnecessary parenthesis" radar as this is a new need it isn't trained for. I could retrain the radar... I see people try to cram too much functionality into one liners. By the time you need to refer to a side effect value more than once in a single statement... Just use multiple statements. It is more clear and easier to debug. Anything else warps human minds trying to read, understand, review, and maintain the code. {see_also: "nested list comprehensions"} '''insert favorite Zen of Python quotes here''' We've got a whitespace delimited language. That is a feature. Lets keep it that way rather than adding a form of (non-curly) braces. I do understand the desire. So far I remain unconvinced of a need. Practical examples from existing code that become clearly easier to understand afterwards instead of made the examples with one letter names may help. 2cents, -gps
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/