New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-42316: Document assignment expression need for ()s #23291
base: main
Are you sure you want to change the base?
Conversation
The list should cover most places where an assignment sub-expression must be wrapped in parentheses. Judging from the statement docs, only a couple of statements do not require that they be wrapped.
Assignment expressions must be surrounded by parentheses when used | ||
as sub-expressions in subscript, slicing, conditional, lambda, | ||
keyword-argument, generator, and comprehension-if expressions. They | ||
can only be used as is in if and while statements and in decorators. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
" and in decorators" must be removed in the 3.8 backport.
@gvanrossum Have there been any other changes that you know of?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. The plan was to look for other places where the parentheses could be omitted without confusing either humans or parsers, but we never did that, and I don't think there are any pressing needs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PEP-572 mentioned the extreme possibilities of requiring parentheses always and only where the parser required them, with the first rejected because if and while uses, as in the doc examples, were thought to be most common. For interactive use, augmented expression statements might be useful: a+= <complexe expression, to echo and bind to something other than _
Most of the sub-expressions requiring parentheses were pretty clearly better with them after I tried without. The most puzzling at the time is why (a:= i+j for j in range(3)) is not allowed while [a:= i+j for j in range(3)] is. If the ge is parenthesized, the parens for the ae look worse for me. But I can imagine that since the ()s are not part of the ge, a no-() ae might be a parsing problem. Even if not, this seems like a rare use since only the last value is accessible outside of the comprehension.
I recently used ass. expr.s for debugging (a use not mentioned in the PEP), to pull multiple values out a complex expression for a single debug print. Positional arguments were easy; indexes required ()s. The latter is probably the first extension of 'no () sub-expressions' I would want looked at.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it makes sense for a[i := f()]
and for a[i:= f(), j := g()]
etc., though I would prefer to require the parentheses in slices (too many colons otherwise :-). Provided we can exclude it in slices, it should be a simple change to the grammar to support it in indexes. Maybe @lysnikolaou can help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that's a very simple change to the parser. I'll have a PR ready later tonight.
Assignment expressions must be surrounded by parentheses when used | ||
as sub-expressions in subscript, slicing, conditional, lambda, | ||
keyword-argument, generator, and comprehension-if expressions. They | ||
can only be used as is in if and while statements and in decorators. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can only be used as is in if and while statements and in decorators. | |
can only be used unparenthesized in if and while statements and in decorators. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. In addition, a list of unparenthesized subexpressions would be shorter: positional arguments in calls and single-value-expressions in set and list displays, including comprehensions, and parenthesized tuples. (Since ()s are not part of the tuple, the difference with ()s surprises me.) Am incomplete list is still safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just apply this so we can land it? You can file a new issue to allow more scenarios.
Also, can you double check that the difference for () and [] is not a regression in the new parser?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is indeed a regression. I'll have a look at this as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this in 3.8.6 as well as 3.10, so it is an oddity but not a regression.
>>> (a:=1, b:=2)
(1, 2)
>>> a:=1, b:=2
SyntaxError: invalid syntax
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a different thing that what I was asking about -- I was asking about
[a := i for i in ...]
(a := i for i in ...)
The former is valid in 3.8 through 3.10, the latter in 3.8 but not in later versions (until @lysnikolaou fixed it earlier today :-). So that's definitely a regression.
The thing you show is due to a generalization of the rule that :=
is not allowed at the top level:
>>> (a := 1)
1
>>> a := 1
File "<stdin>", line 1
a := 1
^
SyntaxError: invalid syntax
I wouldn't want to change that.
This PR probably needs to be updated to reflect the recent changes introduced in #23317 and #23319. Sorry @terryjreedy! |
These are changes I hoped to be able to make ;-). I will add the new in 3.10 item for indexes. |
This PR is stale because it has been open for 30 days with no activity. |
@terryjreedy are you still interested in landing this? A few older comments were never applied.
@@ -1745,6 +1745,11 @@ Or, when processing a file stream in chunks: | |||
while chunk := file.read(9000): | |||
process(chunk) | |||
Assignment expressions must be surrounded by parentheses when used | |||
as sub-expressions in subscript, slicing, conditional, lambda, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first one (subscripts) isn't true in 3.11:
>>> a = [1, 2, 3]
>>> a[x:=1]
2
>>> x
1
@@ -1745,6 +1745,11 @@ Or, when processing a file stream in chunks: | |||
while chunk := file.read(9000): | |||
process(chunk) | |||
Assignment expressions must be surrounded by parentheses when used | |||
as sub-expressions in subscript, slicing, conditional, lambda, | |||
keyword-argument, generator, and comprehension-if expressions. They |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
genexps don't need parens in 3.11:
>>> (x:=3 for y in [])
<generator object <genexpr> at 0x102ae97d0>
They completely disallow assignment expressions for the iterable (not sure why):
>>> (y for y in (z:=[]))
File "<stdin>", line 1
SyntaxError: assignment expression cannot be used in a comprehension iterable expression
>>> (y for y in z:=[])
File "<stdin>", line 1
(y for y in z:=[])
^^
SyntaxError: invalid syntax
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's explained somewhere in PEP 572. "While it’s technically possible to assign consistent semantics to these cases, it’s difficult to determine whether those semantics actually make sense in the absence of real use cases."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh interesting, I think it's actually this part a few paragraphs down: "Due to design constraints in the reference implementation (the symbol table analyser cannot easily detect when names are re-used between the leftmost comprehension iterable expression and the rest of the comprehension), named expressions are disallowed entirely as part of comprehension iterable expressions (the part after each “in”, and before any subsequent “if” or “for” keyword)"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And so yet another wart in the specification was born... :-(
I somehow missed finishing this and would like it updated and merged. The doc is needed; someone just posted code to pydev that was missing required ()s. However, I just discovered that I have been recently de-authorized from merging anything. Jelle, you have been doing great with doc patches, so it would be fine with me if you finished this without waiting for that problem to be fixed. |
Pablo had turned off merges for everyone during the 3.11.0a7 release, but merges are allowed now again. |
The list should cover most places where an assignment sub-expression
must be wrapped in parentheses. Judging from the statement docs,
only a couple of statements do not require that they be wrapped.
https://bugs.python.org/issue42316