[Python-ideas] PEP 572: Statement-Local Name Bindings

Matt Arcidy marcidy at gmail.com
Wed Feb 28 02:46:50 EST 2018


I have been struggling to justify the need based on what I have read.  I
hope this isn't a dupe, I only saw caching mentioned in passing.

Also please excuse some of the naive generalizations below for illustrative
purposes.

Is there a reason memoization doesn't work? If f is truly expensive, using
this syntax only makes sense if you have few comprehensions (as opposed to
many iterations) and few other calls to f. Calling f in 10 comprehensions
would (naively) benefit from memoization more than this.  It appears to me
to be ad-hoc memoization with limited scope.  is this a fair statement?

>From readability, the examples put forth have been to explain the
advantage, with which I agree.  However, i do not believe this scales well.

[(foo(x,y) as g)*(bar(y) as i) + g*foo(x,a) +baz(g,i) for x... for y...]

That's 3 functions, 2 iterators, 3 calls saved ('a' is some constant just
to trigger a new call on foo).  I'm not trying to show ugly statements can
be constructed, but show how quickly in _n iterators and _m functions
readability declines.

it's seems the utility is bounded by f being not complex/costly enough for
memoization, and ( _m, _n) being small enough to pass code review. The
syntax does not create a readability issue, but by adding symbols,
exacerbates an inherent issue with 'complex' comprehensions.

If I have understood, does this bounding on f, _m, and _n yield a tool with
sufficient applicability for a language change?   I know it's already
spoken about as an edge case, I'm just not clear on the bounds of that.  I
am not against it, I just haven't seen this addressed.

Thank you for putting the PEP together.  Again, I don't want to sound
negative on it, I may have misunderstood wildly.  I like the idea
conceptually, and I don't think it's anymore confusing to me than
comprehensions were when I first encountered them, and it will yield a
'cool!' statement much like they did when I learned them.


On Tue, Feb 27, 2018, 22:55 Gregory P. Smith <greg at krypto.org> wrote:

>
> 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
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180228/ae3920c4/attachment-0001.html>


More information about the Python-ideas mailing list