[Python-ideas] PEP 572: about the operator precedence of :=

Tim Peters tim.peters at gmail.com
Thu May 10 00:46:40 EDT 2018


[Guido]
> (I vaguely recall this has been brought up before, but I'm too lazy to find
> the subtread. So it goes.)
>
> PEP 572 currently seems to specify that when used in expressions, the
> precedence of `:=` is lower (i.e. it binds more tightly)

Umm ... that's the opposite of what the Reference Manual says "lower":means:

"""
6.16. Operator precedence

The following table summarizes the operator precedence in Python, from
lowest precedence (least binding) to highest precedence (most
binding).
"""


> than all operators except for the comma.

Which gets more potentially confusing become the comma isn't listed at
all as "an operator".   Instead the meaning of commas for building
tuples is captured by the higher-level `expression-list` grammar
production:

    expression_list ::=  expression ( "," expression )* [","]

So _every_ mere operator "binds more tightly" (if you view it in those
terms) than a comma in an expression list.

What was mostly discussed before - after giving up on fully generally
"assignment expressions" - was whether ":=" should get a precedence
between comparison and bitwise OR operators.  So that, e.g., parens
wouldn't be needed in

    if x := match() is not None:

to get the intended

    if (x := match()) is not None:

But that never gained much traction, and it was dropped quickly.  It
was left with a (possibly unwritten!) consensus that ":=" should bind
very weakly as an operator.  Of the binary infix operators, the most
weakly binding (the weakest of which now is Boolean OR).


> I derive this from the single example
> `stuff = [[y := f(x), x/y] for x in range(5)]`.`

As above, the part you're looking at there falls under the
expression_list part of the grammar, and no binary operator could
group as

    y OP (f(x), x/y)

instead.  All binary operators group as

    (y OP f(x)), (x/y)


> From this it would follow that `f(a := 1, a)`

And now you're throwing in the entirely different meaning of commas in
argument lists ;-)  But same thing:  no operator can cross comma
boundaries in argument lists, because the grammar  of argument lists
doesn't allow for that either.  Even; e.g.,

    f(lambda x: x, 2)

groups as

    f((lambda x: x), 2)


> is equivalent to `a = 1; f(1, 1)`,

Yup.

> and also that `(a := 1, a)` is equivalent to `a = 1; (1, 1)`. (Although
> M.A.L. objected to this.)

That's just another instance of expression_list.

Nothing so far has surprised me, because I have been viewing ":=" as
an operator for some time now.


> But what should `a := 1, 1` at the top level (as a statement) do? On the one
> hand, analogy with the above suggest that it is equivalent to
>`a = 1; (1, 1)`.

If it's an operator, there's really no choice about that.


> But on the other hand, it would be really strange if the following two
> lines had different meanings:
>
>     a = 1, 1   # a = (1, 1)
>     a := 1, 1  # a = 1; (1, 1)

Agreed.


> I now think that the best way out is to rule `:=` in the top level
> expression of an expression statement completely (it would still be okay
> inside parentheses, where it would bind tighter than comma).

Probably prudent, and no objections here.


> An alternative would be to make `:=` bind less tight than comma (like `=`)
> everywhere, so that `a := 1, 1` indeed meant the same as `a = 1, 1`. But
> that feels very wrong at least for the case `f(a := 1, 1)` -- I believe Tim
> already mentioned that we've been conditioned by keyword arguments to parse
> this as `f((a := 1), 1)`. (I could add to this that we have done various
> things to generator expression syntax to avoid ever having to deal with
> ambiguities like `a, a+1 for a in range(10)` or `a for a in x, y`.)

Offhand, since comma is not formally "an operator" now, I expect it
would require major surgery to the grammar to have

    a := 1, 1

group as

    a := (1, 1)

in any context.  At least if ";=" is treated as "just another
operator" and doesn't grow its own unique-to-it pile of grammar rules.


> Another alternative would be to always require parentheses around `:=`, so
> that we would have to write `f((a := 1), 1)`. That's unambiguous, but
> otherwise just gets on the nerves.

I hoped that rigidly calling these "binding expressions" would break
the connection in peoples' minds that these somehow "should behave"
like assignment statements, but that seems futile.  There's really
nothing surprising here _if_ it's viewed as just another operator.
Nobody would be tempted to, e.g., add parens to f(a + 1, 1), or if `+`
were any other binary operator either.  So, over time, it would be
just as annoying need to type them for the `:=` operator.


More information about the Python-ideas mailing list