@@ -104,15 +104,69 @@ def test_named_expression_invalid_17(self):
104
104
with self .assertRaisesRegex (SyntaxError , "invalid syntax" ):
105
105
exec (code , {}, {})
106
106
107
- def test_named_expression_invalid_18 (self ):
107
+ def test_named_expression_invalid_in_class_body (self ):
108
108
code = """class Foo():
109
109
[(42, 1 + ((( j := i )))) for i in range(5)]
110
110
"""
111
111
112
- with self .assertRaisesRegex (TargetScopeError ,
113
- "named expression within a comprehension cannot be used in a class body" ):
112
+ with self .assertRaisesRegex (SyntaxError ,
113
+ "assignment expression within a comprehension cannot be used in a class body" ):
114
114
exec (code , {}, {})
115
115
116
+ def test_named_expression_invalid_rebinding_comprehension_iteration_variable (self ):
117
+ cases = [
118
+ ("Local reuse" , 'i' , "[i := 0 for i in range(5)]" ),
119
+ ("Nested reuse" , 'j' , "[[(j := 0) for i in range(5)] for j in range(5)]" ),
120
+ ("Reuse inner loop target" , 'j' , "[(j := 0) for i in range(5) for j in range(5)]" ),
121
+ ("Unpacking reuse" , 'i' , "[i := 0 for i, j in [(0, 1)]]" ),
122
+ ("Reuse in loop condition" , 'i' , "[i+1 for i in range(5) if (i := 0)]" ),
123
+ ("Unreachable reuse" , 'i' , "[False or (i:=0) for i in range(5)]" ),
124
+ ("Unreachable nested reuse" , 'i' ,
125
+ "[(i, j) for i in range(5) for j in range(5) if True or (i:=10)]" ),
126
+ ]
127
+ for case , target , code in cases :
128
+ msg = f"assignment expression cannot rebind comprehension iteration variable '{ target } '"
129
+ with self .subTest (case = case ):
130
+ with self .assertRaisesRegex (SyntaxError , msg ):
131
+ exec (code , {}, {})
132
+
133
+ def test_named_expression_invalid_rebinding_comprehension_inner_loop (self ):
134
+ cases = [
135
+ ("Inner reuse" , 'j' , "[i for i in range(5) if (j := 0) for j in range(5)]" ),
136
+ ("Inner unpacking reuse" , 'j' , "[i for i in range(5) if (j := 0) for j, k in [(0, 1)]]" ),
137
+ ]
138
+ for case , target , code in cases :
139
+ msg = f"comprehension inner loop cannot rebind assignment expression target '{ target } '"
140
+ with self .subTest (case = case ):
141
+ with self .assertRaisesRegex (SyntaxError , msg ):
142
+ exec (code , {}) # Module scope
143
+ with self .assertRaisesRegex (SyntaxError , msg ):
144
+ exec (code , {}, {}) # Class scope
145
+ with self .assertRaisesRegex (SyntaxError , msg ):
146
+ exec (f"lambda: { code } " , {}) # Function scope
147
+
148
+ def test_named_expression_invalid_comprehension_iterable_expression (self ):
149
+ cases = [
150
+ ("Top level" , "[i for i in (i := range(5))]" ),
151
+ ("Inside tuple" , "[i for i in (2, 3, i := range(5))]" ),
152
+ ("Inside list" , "[i for i in [2, 3, i := range(5)]]" ),
153
+ ("Different name" , "[i for i in (j := range(5))]" ),
154
+ ("Lambda expression" , "[i for i in (lambda:(j := range(5)))()]" ),
155
+ ("Inner loop" , "[i for i in range(5) for j in (i := range(5))]" ),
156
+ ("Nested comprehension" , "[i for i in [j for j in (k := range(5))]]" ),
157
+ ("Nested comprehension condition" , "[i for i in [j for j in range(5) if (j := True)]]" ),
158
+ ("Nested comprehension body" , "[i for i in [(j := True) for j in range(5)]]" ),
159
+ ]
160
+ msg = "assignment expression cannot be used in a comprehension iterable expression"
161
+ for case , code in cases :
162
+ with self .subTest (case = case ):
163
+ with self .assertRaisesRegex (SyntaxError , msg ):
164
+ exec (code , {}) # Module scope
165
+ with self .assertRaisesRegex (SyntaxError , msg ):
166
+ exec (code , {}, {}) # Class scope
167
+ with self .assertRaisesRegex (SyntaxError , msg ):
168
+ exec (f"lambda: { code } " , {}) # Function scope
169
+
116
170
117
171
class NamedExpressionAssignmentTest (unittest .TestCase ):
118
172
@@ -306,39 +360,6 @@ def test_named_expression_scope_11(self):
306
360
self .assertEqual (res , [0 , 1 , 2 , 3 , 4 ])
307
361
self .assertEqual (j , 4 )
308
362
309
- def test_named_expression_scope_12 (self ):
310
- res = [i := i for i in range (5 )]
311
-
312
- self .assertEqual (res , [0 , 1 , 2 , 3 , 4 ])
313
- self .assertEqual (i , 4 )
314
-
315
- def test_named_expression_scope_13 (self ):
316
- res = [i := 0 for i , j in [(1 , 2 ), (3 , 4 )]]
317
-
318
- self .assertEqual (res , [0 , 0 ])
319
- self .assertEqual (i , 0 )
320
-
321
- def test_named_expression_scope_14 (self ):
322
- res = [(i := 0 , j := 1 ) for i , j in [(1 , 2 ), (3 , 4 )]]
323
-
324
- self .assertEqual (res , [(0 , 1 ), (0 , 1 )])
325
- self .assertEqual (i , 0 )
326
- self .assertEqual (j , 1 )
327
-
328
- def test_named_expression_scope_15 (self ):
329
- res = [(i := i , j := j ) for i , j in [(1 , 2 ), (3 , 4 )]]
330
-
331
- self .assertEqual (res , [(1 , 2 ), (3 , 4 )])
332
- self .assertEqual (i , 3 )
333
- self .assertEqual (j , 4 )
334
-
335
- def test_named_expression_scope_16 (self ):
336
- res = [(i := j , j := i ) for i , j in [(1 , 2 ), (3 , 4 )]]
337
-
338
- self .assertEqual (res , [(2 , 2 ), (4 , 4 )])
339
- self .assertEqual (i , 4 )
340
- self .assertEqual (j , 4 )
341
-
342
363
def test_named_expression_scope_17 (self ):
343
364
b = 0
344
365
res = [b := i + b for i in range (5 )]
@@ -421,6 +442,33 @@ def spam():
421
442
422
443
self .assertEqual (ns ["a" ], 20 )
423
444
445
+ def test_named_expression_variable_reuse_in_comprehensions (self ):
446
+ # The compiler is expected to raise syntax error for comprehension
447
+ # iteration variables, but should be fine with rebinding of other
448
+ # names (e.g. globals, nonlocals, other assignment expressions)
449
+
450
+ # The cases are all defined to produce the same expected result
451
+ # Each comprehension is checked at both function scope and module scope
452
+ rebinding = "[x := i for i in range(3) if (x := i) or not x]"
453
+ filter_ref = "[x := i for i in range(3) if x or not x]"
454
+ body_ref = "[x for i in range(3) if (x := i) or not x]"
455
+ nested_ref = "[j for i in range(3) if x or not x for j in range(3) if (x := i)][:-3]"
456
+ cases = [
457
+ ("Rebind global" , f"x = 1; result = { rebinding } " ),
458
+ ("Rebind nonlocal" , f"result, x = (lambda x=1: ({ rebinding } , x))()" ),
459
+ ("Filter global" , f"x = 1; result = { filter_ref } " ),
460
+ ("Filter nonlocal" , f"result, x = (lambda x=1: ({ filter_ref } , x))()" ),
461
+ ("Body global" , f"x = 1; result = { body_ref } " ),
462
+ ("Body nonlocal" , f"result, x = (lambda x=1: ({ body_ref } , x))()" ),
463
+ ("Nested global" , f"x = 1; result = { nested_ref } " ),
464
+ ("Nested nonlocal" , f"result, x = (lambda x=1: ({ nested_ref } , x))()" ),
465
+ ]
466
+ for case , code in cases :
467
+ with self .subTest (case = case ):
468
+ ns = {}
469
+ exec (code , ns )
470
+ self .assertEqual (ns ["x" ], 2 )
471
+ self .assertEqual (ns ["result" ], [0 , 1 , 2 ])
424
472
425
473
if __name__ == "__main__" :
426
474
unittest .main ()
0 commit comments