[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