|
1 | 1 | import doctest
|
| 2 | +import textwrap |
2 | 3 | import unittest
|
3 | 4 |
|
4 | 5 |
|
|
87 | 88 | >>> [None for i in range(10)]
|
88 | 89 | [None, None, None, None, None, None, None, None, None, None]
|
89 | 90 |
|
90 |
| -########### Tests for various scoping corner cases ############ |
91 |
| -
|
92 |
| -Return lambdas that use the iteration variable as a default argument |
93 |
| -
|
94 |
| - >>> items = [(lambda i=i: i) for i in range(5)] |
95 |
| - >>> [x() for x in items] |
96 |
| - [0, 1, 2, 3, 4] |
97 |
| -
|
98 |
| -Same again, only this time as a closure variable |
99 |
| -
|
100 |
| - >>> items = [(lambda: i) for i in range(5)] |
101 |
| - >>> [x() for x in items] |
102 |
| - [4, 4, 4, 4, 4] |
103 |
| -
|
104 |
| -Another way to test that the iteration variable is local to the list comp |
105 |
| -
|
106 |
| - >>> items = [(lambda: i) for i in range(5)] |
107 |
| - >>> i = 20 |
108 |
| - >>> [x() for x in items] |
109 |
| - [4, 4, 4, 4, 4] |
110 |
| -
|
111 |
| -And confirm that a closure can jump over the list comp scope |
112 |
| -
|
113 |
| - >>> items = [(lambda: y) for i in range(5)] |
114 |
| - >>> y = 2 |
115 |
| - >>> [x() for x in items] |
116 |
| - [2, 2, 2, 2, 2] |
117 |
| -
|
118 |
| -We also repeat each of the above scoping tests inside a function: |
119 |
| -
|
120 |
| - >>> def test_func(): |
121 |
| - ... items = [(lambda i=i: i) for i in range(5)] |
122 |
| - ... return [x() for x in items] |
123 |
| - >>> test_func() |
124 |
| - [0, 1, 2, 3, 4] |
125 |
| -
|
126 |
| - >>> def test_func(): |
127 |
| - ... items = [(lambda: i) for i in range(5)] |
128 |
| - ... return [x() for x in items] |
129 |
| - >>> test_func() |
130 |
| - [4, 4, 4, 4, 4] |
131 |
| -
|
132 |
| - >>> def test_func(): |
133 |
| - ... items = [(lambda: i) for i in range(5)] |
134 |
| - ... i = 20 |
135 |
| - ... return [x() for x in items] |
136 |
| - >>> test_func() |
137 |
| - [4, 4, 4, 4, 4] |
138 |
| -
|
139 |
| - >>> def test_func(): |
140 |
| - ... items = [(lambda: y) for i in range(5)] |
141 |
| - ... y = 2 |
142 |
| - ... return [x() for x in items] |
143 |
| - >>> test_func() |
144 |
| - [2, 2, 2, 2, 2] |
145 |
| -
|
146 |
| -And in class scope: |
147 |
| -
|
148 |
| - >>> class C: |
149 |
| - ... items = [(lambda i=i: i) for i in range(5)] |
150 |
| - ... ret = [x() for x in items] |
151 |
| - >>> C.ret |
152 |
| - [0, 1, 2, 3, 4] |
153 |
| -
|
154 |
| - >>> class C: |
155 |
| - ... items = [(lambda: i) for i in range(5)] |
156 |
| - ... ret = [x() for x in items] |
157 |
| - >>> C.ret |
158 |
| - [4, 4, 4, 4, 4] |
159 |
| -
|
160 |
| - >>> class C: |
161 |
| - ... items = [(lambda: i) for i in range(5)] |
162 |
| - ... i = 20 |
163 |
| - ... ret = [x() for x in items] |
164 |
| - >>> C.ret |
165 |
| - [4, 4, 4, 4, 4] |
166 |
| - >>> C.i |
167 |
| - 20 |
168 |
| -
|
169 |
| - >>> class C: |
170 |
| - ... items = [(lambda: y) for i in range(5)] |
171 |
| - ... y = 2 |
172 |
| - ... ret = [x() for x in items] |
173 |
| - >>> C.ret |
174 |
| - [2, 2, 2, 2, 2] |
175 |
| -
|
176 |
| -Some more tests for scoping edge cases, each in func/module/class scope: |
177 |
| -
|
178 |
| - >>> def test_func(): |
179 |
| - ... y = 10 |
180 |
| - ... items = [(lambda: y) for y in range(5)] |
181 |
| - ... x = y |
182 |
| - ... y = 20 |
183 |
| - ... return x, [z() for z in items] |
184 |
| - >>> test_func() |
185 |
| - (10, [4, 4, 4, 4, 4]) |
186 |
| -
|
187 |
| - >>> g = -1 |
188 |
| - >>> def test_func(): |
189 |
| - ... def inner(): |
190 |
| - ... return g |
191 |
| - ... [g for g in range(5)] |
192 |
| - ... return inner |
193 |
| - >>> test_func()() |
194 |
| - -1 |
195 |
| -
|
196 |
| - >>> def test_func(): |
197 |
| - ... x = -1 |
198 |
| - ... items = [(x:=y) for y in range(3)] |
199 |
| - ... return x |
200 |
| - >>> test_func() |
201 |
| - 2 |
202 |
| -
|
203 |
| - >>> def test_func(lst): |
204 |
| - ... ret = [lambda: x for x in lst] |
205 |
| - ... inc = [x + 1 for x in lst] |
206 |
| - ... [x for x in inc] |
207 |
| - ... return ret |
208 |
| - >>> test_func(range(3))[0]() |
209 |
| - 2 |
210 |
| -
|
211 |
| - >>> def test_func(lst): |
212 |
| - ... x = -1 |
213 |
| - ... funcs = [lambda: x for x in lst] |
214 |
| - ... items = [x + 1 for x in lst] |
215 |
| - ... return x |
216 |
| - >>> test_func(range(3)) |
217 |
| - -1 |
218 |
| -
|
219 |
| - >>> def test_func(x): |
220 |
| - ... return [x for x in x] |
221 |
| - >>> test_func([1]) |
222 |
| - [1] |
223 |
| -
|
224 |
| - >>> def test_func(): |
225 |
| - ... x = 1 |
226 |
| - ... def g(): |
227 |
| - ... [x for x in range(3)] |
228 |
| - ... return x |
229 |
| - ... g() |
230 |
| - ... return x |
231 |
| - >>> test_func() |
232 |
| - 1 |
233 |
| -
|
234 | 91 | """
|
235 | 92 |
|
236 | 93 |
|
237 | 94 | class ListComprehensionTest(unittest.TestCase):
|
| 95 | + def _check_in_scopes(self, code, outputs, ns=None, scopes=None): |
| 96 | + code = textwrap.dedent(code) |
| 97 | + scopes = scopes or ["module", "class", "function"] |
| 98 | + for scope in scopes: |
| 99 | + with self.subTest(scope=scope): |
| 100 | + if scope == "class": |
| 101 | + newcode = textwrap.dedent(""" |
| 102 | + class C: |
| 103 | + {code} |
| 104 | + """).format(code=textwrap.indent(code, " ")) |
| 105 | + def get_output(moddict, name): |
| 106 | + return getattr(moddict["C"], name) |
| 107 | + elif scope == "function": |
| 108 | + newcode = textwrap.dedent(""" |
| 109 | + def f(): |
| 110 | + {code} |
| 111 | + return locals() |
| 112 | + """).format(code=textwrap.indent(code, " ")) |
| 113 | + def get_output(moddict, name): |
| 114 | + return moddict["f"]()[name] |
| 115 | + else: |
| 116 | + newcode = code |
| 117 | + def get_output(moddict, name): |
| 118 | + return moddict[name] |
| 119 | + ns = ns or {} |
| 120 | + exec(newcode, ns) |
| 121 | + for k, v in outputs.items(): |
| 122 | + self.assertEqual(get_output(ns, k), v) |
| 123 | + |
| 124 | + def test_lambdas_with_iteration_var_as_default(self): |
| 125 | + code = """ |
| 126 | + items = [(lambda i=i: i) for i in range(5)] |
| 127 | + y = [x() for x in items] |
| 128 | + """ |
| 129 | + outputs = {"y": [0, 1, 2, 3, 4]} |
| 130 | + self._check_in_scopes(code, outputs) |
| 131 | + |
| 132 | + def test_lambdas_with_free_var(self): |
| 133 | + code = """ |
| 134 | + items = [(lambda: i) for i in range(5)] |
| 135 | + y = [x() for x in items] |
| 136 | + """ |
| 137 | + outputs = {"y": [4, 4, 4, 4, 4]} |
| 138 | + self._check_in_scopes(code, outputs) |
| 139 | + |
| 140 | + def test_inner_cell_shadows_outer(self): |
| 141 | + code = """ |
| 142 | + items = [(lambda: i) for i in range(5)] |
| 143 | + i = 20 |
| 144 | + y = [x() for x in items] |
| 145 | + """ |
| 146 | + outputs = {"y": [4, 4, 4, 4, 4]} |
| 147 | + self._check_in_scopes(code, outputs) |
| 148 | + |
| 149 | + def test_closure_can_jump_over_comp_scope(self): |
| 150 | + code = """ |
| 151 | + items = [(lambda: y) for i in range(5)] |
| 152 | + y = 2 |
| 153 | + z = [x() for x in items] |
| 154 | + """ |
| 155 | + outputs = {"z": [2, 2, 2, 2, 2]} |
| 156 | + self._check_in_scopes(code, outputs) |
| 157 | + |
| 158 | + def test_inner_cell_shadows_outer_redefined(self): |
| 159 | + code = """ |
| 160 | + y = 10 |
| 161 | + items = [(lambda: y) for y in range(5)] |
| 162 | + x = y |
| 163 | + y = 20 |
| 164 | + out = [z() for z in items] |
| 165 | + """ |
| 166 | + outputs = {"x": 10, "out": [4, 4, 4, 4, 4]} |
| 167 | + self._check_in_scopes(code, outputs) |
| 168 | + |
| 169 | + def test_shadows_outer_cell(self): |
| 170 | + code = """ |
| 171 | + def inner(): |
| 172 | + return g |
| 173 | + [g for g in range(5)] |
| 174 | + x = inner() |
| 175 | + """ |
| 176 | + outputs = {"x": -1} |
| 177 | + self._check_in_scopes(code, outputs, ns={"g": -1}) |
| 178 | + |
| 179 | + def test_assignment_expression(self): |
| 180 | + code = """ |
| 181 | + x = -1 |
| 182 | + items = [(x:=y) for y in range(3)] |
| 183 | + """ |
| 184 | + outputs = {"x": 2} |
| 185 | + # assignment expression in comprehension is disallowed in class scope |
| 186 | + self._check_in_scopes(code, outputs, scopes=["module", "function"]) |
| 187 | + |
| 188 | + def test_free_var_in_comp_child(self): |
| 189 | + code = """ |
| 190 | + lst = range(3) |
| 191 | + funcs = [lambda: x for x in lst] |
| 192 | + inc = [x + 1 for x in lst] |
| 193 | + [x for x in inc] |
| 194 | + x = funcs[0]() |
| 195 | + """ |
| 196 | + outputs = {"x": 2} |
| 197 | + self._check_in_scopes(code, outputs) |
| 198 | + |
| 199 | + def test_shadow_with_free_and_local(self): |
| 200 | + code = """ |
| 201 | + lst = range(3) |
| 202 | + x = -1 |
| 203 | + funcs = [lambda: x for x in lst] |
| 204 | + items = [x + 1 for x in lst] |
| 205 | + """ |
| 206 | + outputs = {"x": -1} |
| 207 | + self._check_in_scopes(code, outputs) |
| 208 | + |
| 209 | + def test_shadow_comp_iterable_name(self): |
| 210 | + code = """ |
| 211 | + x = [1] |
| 212 | + y = [x for x in x] |
| 213 | + """ |
| 214 | + outputs = {"x": [1]} |
| 215 | + self._check_in_scopes(code, outputs) |
| 216 | + |
| 217 | + def test_nested_free(self): |
| 218 | + code = """ |
| 219 | + x = 1 |
| 220 | + def g(): |
| 221 | + [x for x in range(3)] |
| 222 | + return x |
| 223 | + g() |
| 224 | + """ |
| 225 | + outputs = {"x": 1} |
| 226 | + self._check_in_scopes(code, outputs) |
| 227 | + |
238 | 228 | def test_unbound_local_after_comprehension(self):
|
239 | 229 | def f():
|
240 | 230 | if False:
|
|
0 commit comments