[Python-ideas] PEP 572: Statement-Local Name Bindings
Gregory P. Smith
greg at krypto.org
Wed Feb 28 01:53:05 EST 2018
On Tue, Feb 27, 2018 at 2:35 PM Chris Angelico <rosuav at 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 <rosuav at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180228/d4f7263c/attachment-0001.html>
More information about the Python-ideas
mailing list