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

Chris Angelico rosuav at gmail.com
Wed Feb 28 05:43:05 EST 2018


On Wed, Feb 28, 2018 at 8:04 PM, Robert Vanden Eynde
<robertve92 at gmail.com> wrote:
> Considering the 3rd syntax :
> '(' EXPR 'with' NAME '=' EXPR ')'
>
> Wouldn't have the problem of "execution being middle first and would clearly
> differenciate the "with NAME = CONTEXT" from the "with CONTEXT as NAME:"
> statement.

It's still right-to-left, which is as bad as middle-outward once you
combine it with normal left-to-right evaluation. Python has very
little of this, and usually only in contexts where you wouldn't have
much code on the left:

>>> def d():
...     print("Getting dictionary")
...     return {}
...
>>> def k():
...     print("Getting key")
...     return "spam"
...
>>> def v():
...     print("Getting value")
...     return 42
...
>>> d()[k()] = v()
Getting value
Getting dictionary
Getting key

Python executes the RHS of an assignment statement before the LHS, but
the LHS is usually going to be so simple that you don't really care
(or even notice, usually). By creating a name binding on the right and
then evaluating the left, you create a complicated evaluation order
that *will* have complex code on the left.

> Considering the PEP :
>
> 1) I think I spoke too fast for SqlAlchemy using "where", after looking up,
> they use "filter" (I was pretty sure I read it somewhere...)

There's something with "select where exists" that uses .where(). It
may not be as common as filter, but it's certainly out there.

> 2) talking about the implementation of thektulu in the "where =" part.

?

> 3) "C problem that an equals sign in an expression can now create a name
> binding, rather than performing a comparison." The "=" does variable
> assignement already, and there is no grammar problem of "=" vs "==" because
> the "with" keyword is used in the expression, therefore "with a == ..." is a
> SyntaxError whereas "where a = ..." is alright (See grammar in thektulu
> implemention of "where").

Yes, but in Python, "=" does variable assignment *as a statement*. In
C, you can do this:

while (ch = getch())
    do_something_with(ch)

That's an assignment in an arbitrary condition, and that's a bug
magnet. You cannot do that in Python. You cannot simply miss out one
equals sign and have legal code that does what you don't want. With my
proposed syntax, you'll be able to do this:

while (getch() as ch):
    ...

There's no way that you could accidentally write this when you really
wanted to compare against the character. With yours, I'm not sure
whether it handles a 'while' loop at all, but if it does, it would be
something like:

while (ch with ch = getch()):
    ...

which doesn't read very well, doesn't really save much, but yes, I
agree, it isn't going to accidentally assign.

> Remember that the lexer knows the difference between "=" and "==", so those
> two are clearly different tokens.

It's not the lexer I'm worried about :)

> 4) Would the syntax be allowed after the "for" in a list comprehension ?
>
> [[y, y] for x in range(5) with y = x+1]
>
> This is exactly the same as "for y in [ x+1 ]", allowing the syntax here
> would allow adding "if" to filter in the list comp using the new Variable.
>
> [[y, y] for x in range(5) with y = x+1 if y % 2 == 0]

I honestly don't know. With my "as" syntax, you would be able to,
because it's simply first-use. The (expr as name) unit is itself an
expression with a value. The 'with' clause has to bracket the value in
some way.

> 5) Any expression vs "post for" only
>
> When I say "any expression" I mean:
>
> print(y+2 with y = x+1)
>
> When I say "post for in list comp" I mean the previous paragraph:
>
> [y+2 for x in range(5) with y = x+1]
>
> Allowing both cases leads to having two ways in the simple case [(y,y) with
> y = x+1 for x in range(5)] vs [(y,y) for x in range(5) with y = x+1]  (but
> that's alright)
>
> Allowing "any expression" would result in having two ways to have variable
> assignement :
>
> y = x + 1
> print(y+2)
>
> Vs:
>
> print(y+2 with y = x+1)
>
> One could argue the first is imperative programming whereas the second is
> Functional programming.
>
> The second case will have to have "y" being a Local variable as the new
> Variable in list comp are not in the outside scope.

I don't know what the benefit is here, but sure. As long as the
grammar is unambiguous, I don't see any particular reason to reject
this.

> 6) with your syntax, how does the simple case work (y+2 with y = x+1) ?
>
> Would you write ((x+1 as y) + 2) ? That's very unclear where the variable
> are defined, in the [(x+1 as y), y] case, the scoping would suggest the "y"
> Variable is defined between the parenthesis whereas [x+1 as y, y] is not
> symmetric.

What simple case? The case where you only use the variable once? I'd
write it like this:

(x + 1) + 2

> The issue is not only about reusing variable.

If you aren't using the variable multiple times, there's no point
giving it a name. Unless I'm missing something here?

> 7) the "lambda example", the "v" variable can be renamed "y" to be
> consistent with the other examples.

Oops, thanks, fixed.

> 8) there are two ways of using a lamba, either positional args, either
> keyword arguments, writing
>
>  (lambda y: [y, y])(x+1)
>
> Vs
>
> (lambda y: [y, y])(y=x+1)
>
> In the second example, the y = x+1 is explicit.

Ewww. Remind me what the benefit is of writing the variable name that
many times? "Explicit" doesn't mean "utterly verbose".

> 10) Chaining, in the case of the "with =", in thektulu, parenthesis were
> mandatory:
>
> print((z+3 with z = y+2) with y = x+2)
>
> What happens when the parenthesis are dropped ?
>
> print(z+3 with y = x+2 with z = y+2)
>
> Vs
>
> print(z+3 with y = x+2 with z = y+2)
>
> I prefer the first one be cause it's in the same order as the "post for"
>
> [z + 3 for y in [ x+2 ] for z in [ y+2 ]]

With my proposal, the parens are simply mandatory. Extending this to
make them optional can come later.

> 11) Scoping, in the case of the "with =" syntax, I think the parenthesis
> introduce a scope :
>
> print(y + (y+1 where y = 2))
>
> Would raise a SyntaxError, it's probably better for the variable beeing
> local and not in the current function (that would be a mess).
>
> Remember that in list comp, the variable is not leaked :
>
> x = 5
> stuff = [y+2 for y in [x+1]
> print(y) # SyntaxError

Scoping is a fundamental part of both my proposal and the others I've
seen here. (BTW, that would be a NameError, not a SyntaxError; it's
perfectly legal to ask for the name 'y', it just hasn't been given any
value.) By my definition, the variable is locked to the statement that
created it, even if that's a compound statement. By the definition of
a "(expr given var = expr)" proposal, it would be locked to that
single expression.

ChrisA


More information about the Python-ideas mailing list