PEP 572: about the operator precedence of :=
(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) than all operators except for the comma. I derive this from the single example `stuff = [[y := f(x), x/y] for x in range(5)]`.
From this it would follow that `f(a := 1, a)` is equivalent to `a = 1; f(1, 1)`, and also that `(a := 1, a)` is equivalent to `a = 1; (1, 1)`. (Although M.A.L. objected to this.)
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)`. 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) 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). 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`.) 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. -- --Guido van Rossum (python.org/~guido)
On Thu, May 10, 2018 at 1:33 PM, Guido van Rossum <guido@python.org> wrote:
(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) than all operators except for the comma. I derive this from the single example `stuff = [[y := f(x), x/y] for x in range(5)]`.
From this it would follow that `f(a := 1, a)` is equivalent to `a = 1; f(1, 1)`, and also that `(a := 1, a)` is equivalent to `a = 1; (1, 1)`. (Although M.A.L. objected to this.)
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)`. 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)
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).
I would have := bind more tightly than the comma. Consider: a = 1, x := 2, 3 IMO the only sane interpretation is "x = 2; a = 1, 2, 3". Effectively, the := operator does not like to play with commas; we've already ruled out "a, b := range(2)" as a means of unpacking, so it makes more sense to have that simply mean "b = range(2); a, b". ChrisA
On Wed, May 9, 2018 at 8:42 PM, Chris Angelico <rosuav@gmail.com> wrote:
On Thu, May 10, 2018 at 1:33 PM, Guido van Rossum <guido@python.org> wrote:
(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) than all operators except for the comma. I derive this from the single example `stuff = [[y := f(x), x/y] for x in range(5)]`.
From this it would follow that `f(a := 1, a)` is equivalent to `a = 1; f(1, 1)`, and also that `(a := 1, a)` is equivalent to `a = 1; (1, 1)`. (Although M.A.L. objected to this.)
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)`. 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)
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).
I would have := bind more tightly than the comma. Consider:
a = 1, x := 2, 3
IMO the only sane interpretation is "x = 2; a = 1, 2, 3". Effectively, the := operator does not like to play with commas; we've already ruled out "a, b := range(2)" as a means of unpacking, so it makes more sense to have that simply mean "b = range(2); a, b".
Oh, I hadn't even though of combining the two in one statement. That example is truly horrible (on first skim I didn't even notice it used two different assignment operators!) and strengthens my confidence that we should just disallow an un-parenthesized `:=` operator at the top level, where now the top level includes the RHS of a classic assignment. -- --Guido van Rossum (python.org/~guido)
On 10.05.2018 05:52, Guido van Rossum wrote:
I would have := bind more tightly than the comma. Consider:
a = 1, x := 2, 3
IMO the only sane interpretation is "x = 2; a = 1, 2, 3". Effectively, the := operator does not like to play with commas; we've already ruled out "a, b := range(2)" as a means of unpacking, so it makes more sense to have that simply mean "b = range(2); a, b".
Oh, I hadn't even though of combining the two in one statement. That example is truly horrible (on first skim I didn't even notice it used two different assignment operators!) and strengthens my confidence that we should just disallow an un-parenthesized `:=` operator at the top level, where now the top level includes the RHS of a classic assignment.
Yes, please, and ideally not only at the top level, but everywhere. To a (former Pascal) programmer, a := 1 doesn't read like an operator. It's an assignment expression. If embedded expressions is where Python is heading, it should be made very clear where the embedded expression starts and where it ends on a line. Anything else will just result in hard to debug code with subtle errors... oops = 1, a := 2 * 3, 4, 5 - 6 By having to write: ahhh = 1, (a := 2) * 3, 4, 5 - 6 you at least know that "(a := 2)" will evaluate to 2 in the tuple on the right side. Not sure whether this was discussed before (I'm not really following the discussion), but what happens if you write: check = 0 and (a := 1) ? Will "a" get assigned or not ? All that said, I sometimes do wonder what people dislike so much about explicit for-loops which make all of these things obvious even to a complete Python newbie reading the code. IMO, line noise is not something Python should strive for. Other languages such as APL are much better at that - just look at the beauty of "(~R∊R∘.×R)/R←1↓ιR" ;-) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 10 2018)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On Thu, May 10, 2018 at 7:04 PM, M.-A. Lemburg <mal@egenix.com> wrote:
Not sure whether this was discussed before (I'm not really following the discussion), but what happens if you write:
check = 0 and (a := 1)
? Will "a" get assigned or not ?
No, it won't. It's the same as any other side effects after an 'and' - that expression is completely not evaluated. ChrisA
On Thu, May 10, 2018 at 5:04 AM, M.-A. Lemburg <mal@egenix.com> wrote:
To a (former Pascal) programmer, a := 1 doesn't read like an operator. It's an assignment expression. If embedded expressions is where Python is heading, it should be made very clear where the embedded expression starts and where it ends on a line.
The rules we've arrived at are about as straightforward as it gets: the RHS of `:=` ends at the nearest comma or close parenthesis/bracket/brace. OT about the name: despite Tim's relentless pushing of "binding expressions" in the end I think they should be called "assignment expressions" just like in C. -- --Guido van Rossum (python.org/~guido)
On 10.05.2018 15:57, Guido van Rossum wrote:
On Thu, May 10, 2018 at 5:04 AM, M.-A. Lemburg <mal@egenix.com> wrote:
To a (former Pascal) programmer, a := 1 doesn't read like an operator. It's an assignment expression. If embedded expressions is where Python is heading, it should be made very clear where the embedded expression starts and where it ends on a line.
The rules we've arrived at are about as straightforward as it gets: the RHS of `:=` ends at the nearest comma or close parenthesis/bracket/brace.
That may be easy for a computer to parse, but it's not for a programmer. It would be better to contain such expressions inside a safe container which is clearly visible to a human eye. ohoh = a := (1, 2, 3), 4, a * 2 vs. aha = ((a := (1, 2, 3)), 4, a * 2) You'd simplify the above logic to: the RHS of ":=" ends at the nearest closing parenthesis.
OT about the name: despite Tim's relentless pushing of "binding expressions" in the end I think they should be called "assignment expressions" just like in C.
-- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 10 2018)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On 05/10/2018 09:29 AM, M.-A. Lemburg wrote:
On 10.05.2018 15:57, Guido van Rossum wrote:
On Thu, May 10, 2018 at 5:04 AM, M.-A. Lemburg wrote:
To a (former Pascal) programmer, a := 1 doesn't read like an operator. It's an assignment expression. If embedded expressions is where Python is heading, it should be made very clear where the embedded expression starts and where it ends on a line.
The rules we've arrived at are about as straightforward as it gets: the RHS of `:=` ends at the nearest comma or close parenthesis/bracket/brace.
That may be easy for a computer to parse, but it's not for a programmer. It would be better to contain such expressions inside a safe container which is clearly visible to a human eye.
ohoh = a := (1, 2, 3), 4, a * 2
I have no problem reading that.
vs.
aha = ((a := (1, 2, 3)), 4, a * 2)
The extra parens are unneeded line noise (at least for me). -- ~Ethan~
On 10 May 2018 at 17:38, Ethan Furman <ethan@stoneleaf.us> wrote:
On 05/10/2018 09:29 AM, M.-A. Lemburg wrote:
On 10.05.2018 15:57, Guido van Rossum wrote:
On Thu, May 10, 2018 at 5:04 AM, M.-A. Lemburg wrote:
To a (former Pascal) programmer, a := 1 doesn't read like an operator. It's an assignment expression. If embedded expressions is where Python is heading, it should be made very clear where the embedded expression starts and where it ends on a line.
The rules we've arrived at are about as straightforward as it gets: the RHS of `:=` ends at the nearest comma or close parenthesis/bracket/brace.
That may be easy for a computer to parse, but it's not for a programmer. It would be better to contain such expressions inside a safe container which is clearly visible to a human eye.
ohoh = a := (1, 2, 3), 4, a * 2
I have no problem reading that.
vs.
aha = ((a := (1, 2, 3)), 4, a * 2)
The extra parens are unneeded line noise (at least for me).
I found the version with extra parens *harder* to read. Although I will admit neither is particularly easy to read (not surprising as it's an artificial example intended to prove a point, not a real world use case) and in practice I'd write a = 1, 2, 3 ohoh = a, 4, a * 2 Look ma, no parens! Paul
[Guido]
,,, OT about the name: despite Tim's relentless pushing of "binding expressions" in the end I think they should be called "assignment expressions" just like in C.
Ha! I already gave up on "binding expressions". For nearly a full day, I've been rigidly calling them "binding operators" instead. That's why everyone has warmed up to them so dramatically since yesterday. Oh. They haven't? Oh well - since verbal misdirection failed, I'll go back to "assignment expressions'. Chris always liked that better anyway too.
On Wed, May 9, 2018, 11:53 PM Guido van Rossum <guido@python.org> wrote:
Oh, I hadn't even though of combining the two in one statement. That example is truly horrible (on first skim I didn't even notice it used two different assignment operators!) and strengthens my confidence that we should just disallow an un-parenthesized `:=` operator at the top level, where now the top level includes the RHS of a classic assignment.
IMO, I would strongly prefer calling an unparenthesized top-level ':=' a SyntaxError.
[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.
Tim Peters wrote:
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). """
Which is also in accord with the usual English meaning of the word "precedence" -- things with higher precedence get done first. -- Greg
On 5/9/2018 11:33 PM, Guido van Rossum wrote:
I now think that the best way out is to rule `:=` in the top level expression of an expression statement completely
I would like to be able to interactively enter
a: = f(2,4)
to have 'a' echoed as well as bound. -- Terry Jan Reedy
On Thu, May 10, 2018 at 3:32 AM, Terry Reedy <tjreedy@udel.edu> wrote:
On 5/9/2018 11:33 PM, Guido van Rossum wrote:
I now think that the best way out is to rule `:=` in the top level
expression of an expression statement completely
I would like to be able to interactively enter
a: = f(2,4)
to have 'a' echoed as well as bound.
I hope that's a typo (the can be no space between `:` and `=`, since `:=` is a single token, just like `<='). We *could* make this work while still ruling out the ambiguous cases (which involve top-level commas on either side of the assignment expression). OTOH I worry that this particular feature would cause `:=` to become part of many a teacher's bag of tricks to show off, exposing users to it way too early for any curriculum, and it might then elicit complaints that
def f(): ... a := 5 ... f()
doesn't print `5`. So all in all I'm not sure I think this is important enough to support, and the rule "Use `:=` in expressions, not as a top level assignment" seems easier to explain and understand. -- --Guido van Rossum (python.org/~guido)
On 5/10/2018 9:53 AM, Guido van Rossum wrote:
On Thu, May 10, 2018 at 3:32 AM, Terry Reedy <tjreedy@udel.edu <mailto:tjreedy@udel.edu>> wrote:
On 5/9/2018 11:33 PM, Guido van Rossum wrote:
I now think that the best way out is to rule `:=` in the top level expression of an expression statement completely
I would like to be able to interactively enter
>>> a: = f(2,4)
to have 'a' echoed as well as bound.
I hope that's a typo (the can be no space between `:` and `=`, since `:=` is a single token, just like `<=').
a := f(2,4) # corrected ;-)
We *could* make this work while still ruling out the ambiguous cases (which involve top-level commas on either side of the assignment expression).
OTOH I worry that this particular feature would cause `:=` to become part of many a teacher's bag of tricks to show off,
until someone tries a := [0]*10000000
exposing users to it way too early for any curriculum, > and it might then elicit complaints that
def f(): ... a := 5 ... f()
doesn't print `5`.
Although the reason the same as for any expression, I can believe that people will see it as different. A bare assignment expression *looks* like a statement in a way that other expressions do not.
So all in all I'm not sure I think this is important enough to support, and the rule "Use `:=` in expressions, not as a top level assignment" seems easier to explain and understand.
-- Terry Jan Reedy
On Thu, May 10, 2018 at 09:53:40AM -0400, Guido van Rossum wrote: [...]
So all in all I'm not sure I think this is important enough to support, and the rule "Use `:=` in expressions, not as a top level assignment" seems easier to explain and understand.
Like Terry, I too would find it useful to be able to use x := expression in the interactive interpreter to see the value immediately. But I'd rather start off by prohibiting := as a stand-alone statement, and relax the restriction later, than by allowing it and then regretting it later. I think the rule "binding expressions are prohibited as a stand-alone statement" might reduce some of the anxiety about having two ways to do assignment, even if the cost is that they are treated as a special case. (All other expressions are allowed as statements, even if they're pointless.) -- Steve
Le 10/05/2018 à 09:32, Terry Reedy a écrit :
On 5/9/2018 11:33 PM, Guido van Rossum wrote:
I now think that the best way out is to rule `:=` in the top level expression of an expression statement completely
I would like to be able to interactively enter
a: = f(2,4)
to have 'a' echoed as well as bound.
This behaviour is reachable by using: print(a := f(2,4)) Otherwise, I can see coming a new wave of devs saying assignment with "=" is outdated, and we should use the new assignment operator ":=". Cleaning code by removing useless prints is something, having to go through the syntax to remove the ":" of some ":=", and having to test everything again to be sure we didn't break anything is another matter (long live unit tests! But still...). Side note: I wouldn't recommand to use `print(a := f(2,4))` either, as it would be a print-with-a-side-effect. Which would then also break the code if removed the usual way by deleting the line (or commenting it if you like messy). Maybe, for another proposal of course, we could allow the shell to be more verbose, by printing any explicit assignment (whether we used "=" or ":=" wouldn't matter)? -Brice
On 10 May 2018 at 13:33, Guido van Rossum <guido@python.org> wrote:
(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) than all operators except for the comma. I derive this from the single example `stuff = [[y := f(x), x/y] for x in range(5)]`.
From this it would follow that `f(a := 1, a)` is equivalent to `a = 1; f(1, 1)`, and also that `(a := 1, a)` is equivalent to `a = 1; (1, 1)`. (Although M.A.L. objected to this.)
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)`. 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)
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).
FWIW, this is one of the ambiguities that the generalised postfix expression form of the given clause would reduce fairly significantly by separating the result expression from the bound expression: a = 1, 1 a given a = 1, 1 # As above, but also returns a a = 1, x, 3 given x = 2 They also compose fairly nicely as an essentially right associative operator: a given a = 1, x, 3 given x = 2 a given a = (1, x, 3 given x = 2) # Same as previous (a given a = 1), x, 3 given x = 2 # Forcing left-associativity While you do have to repeat the bound name at least once, you gain a much clearer order of execution (while it's an order of execution that's right-to-left rather than left-to-right, "rightmost expression first" is the normal way assignments get evaluated anyway). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (12)
-
Brice Parent
-
Chris Angelico
-
David Mertz
-
Ethan Furman
-
Greg Ewing
-
Guido van Rossum
-
M.-A. Lemburg
-
Nick Coghlan
-
Paul Moore
-
Steven D'Aprano
-
Terry Reedy
-
Tim Peters