Skip to content

Commit 87c87b5

Browse files
authored
[3.9] bpo-42381: Allow walrus in set literals and set comprehensions (GH-23332) (GH-23333)
Currently walruses are not allowerd in set literals and set comprehensions: >>> {y := 4, 4**2, 3**3} File "<stdin>", line 1 {y := 4, 4**2, 3**3} ^ SyntaxError: invalid syntax but they should be allowed as well per PEP 572. (cherry picked from commit b0aba1f) Co-authored-by: Pablo Galindo <[email protected]>
1 parent 36619e1 commit 87c87b5

File tree

4 files changed

+1016
-1126
lines changed

4 files changed

+1016
-1126
lines changed

Grammar/python.gram

+2-3
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ block[asdl_seq*] (memo):
301301
| simple_stmt
302302
| invalid_block
303303

304-
expressions_list[asdl_seq*]: a=','.star_expression+ [','] { a }
305304
star_expressions[expr_ty]:
306305
| a=star_expression b=(',' c=star_expression { c })+ [','] {
307306
_Py_Tuple(CHECK(_PyPegen_seq_insert_in_front(p, a, b)), Load, EXTRA) }
@@ -505,9 +504,9 @@ group[expr_ty]:
505504
genexp[expr_ty]:
506505
| '(' a=named_expression ~ b=for_if_clauses ')' { _Py_GeneratorExp(a, b, EXTRA) }
507506
| invalid_comprehension
508-
set[expr_ty]: '{' a=expressions_list '}' { _Py_Set(a, EXTRA) }
507+
set[expr_ty]: '{' a=star_named_expressions '}' { _Py_Set(a, EXTRA) }
509508
setcomp[expr_ty]:
510-
| '{' a=expression ~ b=for_if_clauses '}' { _Py_SetComp(a, b, EXTRA) }
509+
| '{' a=named_expression ~ b=for_if_clauses '}' { _Py_SetComp(a, b, EXTRA) }
511510
| invalid_comprehension
512511
dict[expr_ty]:
513512
| '{' a=[double_starred_kvpairs] '}' {

Lib/test/test_named_expressions.py

+57-3
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def test_named_expression_invalid_in_class_body(self):
113113
"assignment expression within a comprehension cannot be used in a class body"):
114114
exec(code, {}, {})
115115

116-
def test_named_expression_invalid_rebinding_comprehension_iteration_variable(self):
116+
def test_named_expression_invalid_rebinding_list_comprehension_iteration_variable(self):
117117
cases = [
118118
("Local reuse", 'i', "[i := 0 for i in range(5)]"),
119119
("Nested reuse", 'j', "[[(j := 0) for i in range(5)] for j in range(5)]"),
@@ -130,7 +130,7 @@ def test_named_expression_invalid_rebinding_comprehension_iteration_variable(sel
130130
with self.assertRaisesRegex(SyntaxError, msg):
131131
exec(code, {}, {})
132132

133-
def test_named_expression_invalid_rebinding_comprehension_inner_loop(self):
133+
def test_named_expression_invalid_rebinding_list_comprehension_inner_loop(self):
134134
cases = [
135135
("Inner reuse", 'j', "[i for i in range(5) if (j := 0) for j in range(5)]"),
136136
("Inner unpacking reuse", 'j', "[i for i in range(5) if (j := 0) for j, k in [(0, 1)]]"),
@@ -145,7 +145,7 @@ def test_named_expression_invalid_rebinding_comprehension_inner_loop(self):
145145
with self.assertRaisesRegex(SyntaxError, msg):
146146
exec(f"lambda: {code}", {}) # Function scope
147147

148-
def test_named_expression_invalid_comprehension_iterable_expression(self):
148+
def test_named_expression_invalid_list_comprehension_iterable_expression(self):
149149
cases = [
150150
("Top level", "[i for i in (i := range(5))]"),
151151
("Inside tuple", "[i for i in (2, 3, i := range(5))]"),
@@ -167,6 +167,60 @@ def test_named_expression_invalid_comprehension_iterable_expression(self):
167167
with self.assertRaisesRegex(SyntaxError, msg):
168168
exec(f"lambda: {code}", {}) # Function scope
169169

170+
def test_named_expression_invalid_rebinding_set_comprehension_iteration_variable(self):
171+
cases = [
172+
("Local reuse", 'i', "{i := 0 for i in range(5)}"),
173+
("Nested reuse", 'j', "{{(j := 0) for i in range(5)} for j in range(5)}"),
174+
("Reuse inner loop target", 'j', "{(j := 0) for i in range(5) for j in range(5)}"),
175+
("Unpacking reuse", 'i', "{i := 0 for i, j in {(0, 1)}}"),
176+
("Reuse in loop condition", 'i', "{i+1 for i in range(5) if (i := 0)}"),
177+
("Unreachable reuse", 'i', "{False or (i:=0) for i in range(5)}"),
178+
("Unreachable nested reuse", 'i',
179+
"{(i, j) for i in range(5) for j in range(5) if True or (i:=10)}"),
180+
]
181+
for case, target, code in cases:
182+
msg = f"assignment expression cannot rebind comprehension iteration variable '{target}'"
183+
with self.subTest(case=case):
184+
with self.assertRaisesRegex(SyntaxError, msg):
185+
exec(code, {}, {})
186+
187+
def test_named_expression_invalid_rebinding_set_comprehension_inner_loop(self):
188+
cases = [
189+
("Inner reuse", 'j', "{i for i in range(5) if (j := 0) for j in range(5)}"),
190+
("Inner unpacking reuse", 'j', "{i for i in range(5) if (j := 0) for j, k in {(0, 1)}}"),
191+
]
192+
for case, target, code in cases:
193+
msg = f"comprehension inner loop cannot rebind assignment expression target '{target}'"
194+
with self.subTest(case=case):
195+
with self.assertRaisesRegex(SyntaxError, msg):
196+
exec(code, {}) # Module scope
197+
with self.assertRaisesRegex(SyntaxError, msg):
198+
exec(code, {}, {}) # Class scope
199+
with self.assertRaisesRegex(SyntaxError, msg):
200+
exec(f"lambda: {code}", {}) # Function scope
201+
202+
def test_named_expression_invalid_set_comprehension_iterable_expression(self):
203+
cases = [
204+
("Top level", "{i for i in (i := range(5))}"),
205+
("Inside tuple", "{i for i in (2, 3, i := range(5))}"),
206+
("Inside list", "{i for i in {2, 3, i := range(5)}}"),
207+
("Different name", "{i for i in (j := range(5))}"),
208+
("Lambda expression", "{i for i in (lambda:(j := range(5)))()}"),
209+
("Inner loop", "{i for i in range(5) for j in (i := range(5))}"),
210+
("Nested comprehension", "{i for i in {j for j in (k := range(5))}}"),
211+
("Nested comprehension condition", "{i for i in {j for j in range(5) if (j := True)}}"),
212+
("Nested comprehension body", "{i for i in {(j := True) for j in range(5)}}"),
213+
]
214+
msg = "assignment expression cannot be used in a comprehension iterable expression"
215+
for case, code in cases:
216+
with self.subTest(case=case):
217+
with self.assertRaisesRegex(SyntaxError, msg):
218+
exec(code, {}) # Module scope
219+
with self.assertRaisesRegex(SyntaxError, msg):
220+
exec(code, {}, {}) # Class scope
221+
with self.assertRaisesRegex(SyntaxError, msg):
222+
exec(f"lambda: {code}", {}) # Function scope
223+
170224

171225
class NamedExpressionAssignmentTest(unittest.TestCase):
172226

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allow assignment expressions in set literals and set comprehensions as per
2+
PEP 572. Patch by Pablo Galindo.

0 commit comments

Comments
 (0)