Skip to content

Commit

Permalink
run all listcomp scope tests in module, class, and func scope
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm committed Mar 8, 2023
1 parent b6a025b commit 90b34de
Showing 1 changed file with 134 additions and 144 deletions.
278 changes: 134 additions & 144 deletions Lib/test/test_listcomps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import doctest
import textwrap
import unittest


Expand Down Expand Up @@ -87,154 +88,143 @@
>>> [None for i in range(10)]
[None, None, None, None, None, None, None, None, None, None]
########### Tests for various scoping corner cases ############
Return lambdas that use the iteration variable as a default argument
>>> items = [(lambda i=i: i) for i in range(5)]
>>> [x() for x in items]
[0, 1, 2, 3, 4]
Same again, only this time as a closure variable
>>> items = [(lambda: i) for i in range(5)]
>>> [x() for x in items]
[4, 4, 4, 4, 4]
Another way to test that the iteration variable is local to the list comp
>>> items = [(lambda: i) for i in range(5)]
>>> i = 20
>>> [x() for x in items]
[4, 4, 4, 4, 4]
And confirm that a closure can jump over the list comp scope
>>> items = [(lambda: y) for i in range(5)]
>>> y = 2
>>> [x() for x in items]
[2, 2, 2, 2, 2]
We also repeat each of the above scoping tests inside a function:
>>> def test_func():
... items = [(lambda i=i: i) for i in range(5)]
... return [x() for x in items]
>>> test_func()
[0, 1, 2, 3, 4]
>>> def test_func():
... items = [(lambda: i) for i in range(5)]
... return [x() for x in items]
>>> test_func()
[4, 4, 4, 4, 4]
>>> def test_func():
... items = [(lambda: i) for i in range(5)]
... i = 20
... return [x() for x in items]
>>> test_func()
[4, 4, 4, 4, 4]
>>> def test_func():
... items = [(lambda: y) for i in range(5)]
... y = 2
... return [x() for x in items]
>>> test_func()
[2, 2, 2, 2, 2]
And in class scope:
>>> class C:
... items = [(lambda i=i: i) for i in range(5)]
... ret = [x() for x in items]
>>> C.ret
[0, 1, 2, 3, 4]
>>> class C:
... items = [(lambda: i) for i in range(5)]
... ret = [x() for x in items]
>>> C.ret
[4, 4, 4, 4, 4]
>>> class C:
... items = [(lambda: i) for i in range(5)]
... i = 20
... ret = [x() for x in items]
>>> C.ret
[4, 4, 4, 4, 4]
>>> C.i
20
>>> class C:
... items = [(lambda: y) for i in range(5)]
... y = 2
... ret = [x() for x in items]
>>> C.ret
[2, 2, 2, 2, 2]
Some more tests for scoping edge cases, each in func/module/class scope:
>>> def test_func():
... y = 10
... items = [(lambda: y) for y in range(5)]
... x = y
... y = 20
... return x, [z() for z in items]
>>> test_func()
(10, [4, 4, 4, 4, 4])
>>> g = -1
>>> def test_func():
... def inner():
... return g
... [g for g in range(5)]
... return inner
>>> test_func()()
-1
>>> def test_func():
... x = -1
... items = [(x:=y) for y in range(3)]
... return x
>>> test_func()
2
>>> def test_func(lst):
... ret = [lambda: x for x in lst]
... inc = [x + 1 for x in lst]
... [x for x in inc]
... return ret
>>> test_func(range(3))[0]()
2
>>> def test_func(lst):
... x = -1
... funcs = [lambda: x for x in lst]
... items = [x + 1 for x in lst]
... return x
>>> test_func(range(3))
-1
>>> def test_func(x):
... return [x for x in x]
>>> test_func([1])
[1]
>>> def test_func():
... x = 1
... def g():
... [x for x in range(3)]
... return x
... g()
... return x
>>> test_func()
1
"""


class ListComprehensionTest(unittest.TestCase):
def _check_in_scopes(self, code, outputs, ns=None, scopes=None):
code = textwrap.dedent(code)
scopes = scopes or ["module", "class", "function"]
for scope in scopes:
with self.subTest(scope=scope):
if scope == "class":
newcode = textwrap.dedent("""
class C:
{code}
""").format(code=textwrap.indent(code, " "))
def get_output(moddict, name):
return getattr(moddict["C"], name)
elif scope == "function":
newcode = textwrap.dedent("""
def f():
{code}
return locals()
""").format(code=textwrap.indent(code, " "))
def get_output(moddict, name):
return moddict["f"]()[name]
else:
newcode = code
def get_output(moddict, name):
return moddict[name]
ns = ns or {}
exec(newcode, ns)
for k, v in outputs.items():
self.assertEqual(get_output(ns, k), v)

def test_lambdas_with_iteration_var_as_default(self):
code = """
items = [(lambda i=i: i) for i in range(5)]
y = [x() for x in items]
"""
outputs = {"y": [0, 1, 2, 3, 4]}
self._check_in_scopes(code, outputs)

def test_lambdas_with_free_var(self):
code = """
items = [(lambda: i) for i in range(5)]
y = [x() for x in items]
"""
outputs = {"y": [4, 4, 4, 4, 4]}
self._check_in_scopes(code, outputs)

def test_inner_cell_shadows_outer(self):
code = """
items = [(lambda: i) for i in range(5)]
i = 20
y = [x() for x in items]
"""
outputs = {"y": [4, 4, 4, 4, 4]}
self._check_in_scopes(code, outputs)

def test_closure_can_jump_over_comp_scope(self):
code = """
items = [(lambda: y) for i in range(5)]
y = 2
z = [x() for x in items]
"""
outputs = {"z": [2, 2, 2, 2, 2]}
self._check_in_scopes(code, outputs)

def test_inner_cell_shadows_outer_redefined(self):
code = """
y = 10
items = [(lambda: y) for y in range(5)]
x = y
y = 20
out = [z() for z in items]
"""
outputs = {"x": 10, "out": [4, 4, 4, 4, 4]}
self._check_in_scopes(code, outputs)

def test_shadows_outer_cell(self):
code = """
def inner():
return g
[g for g in range(5)]
x = inner()
"""
outputs = {"x": -1}
self._check_in_scopes(code, outputs, ns={"g": -1})

def test_assignment_expression(self):
code = """
x = -1
items = [(x:=y) for y in range(3)]
"""
outputs = {"x": 2}
# assignment expression in comprehension is disallowed in class scope
self._check_in_scopes(code, outputs, scopes=["module", "function"])

def test_free_var_in_comp_child(self):
code = """
lst = range(3)
funcs = [lambda: x for x in lst]
inc = [x + 1 for x in lst]
[x for x in inc]
x = funcs[0]()
"""
outputs = {"x": 2}
self._check_in_scopes(code, outputs)

def test_shadow_with_free_and_local(self):
code = """
lst = range(3)
x = -1
funcs = [lambda: x for x in lst]
items = [x + 1 for x in lst]
"""
outputs = {"x": -1}
self._check_in_scopes(code, outputs)

def test_shadow_comp_iterable_name(self):
code = """
x = [1]
y = [x for x in x]
"""
outputs = {"x": [1]}
self._check_in_scopes(code, outputs)

def test_nested_free(self):
code = """
x = 1
def g():
[x for x in range(3)]
return x
g()
"""
outputs = {"x": 1}
self._check_in_scopes(code, outputs)

def test_unbound_local_after_comprehension(self):
def f():
if False:
Expand Down

0 comments on commit 90b34de

Please sign in to comment.