Skip to content
14 changes: 7 additions & 7 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2063,7 +2063,7 @@ def test_multiline_async_generator_expression(self):

def test_multiline_list_comprehension(self):
snippet = textwrap.dedent("""\
[(x,
_ = [(x,
2*x)
for x
in [1,2,3] if (x > 0
Expand All @@ -2073,14 +2073,14 @@ def test_multiline_list_comprehension(self):
compiled_code, _ = self.check_positions_against_ast(snippet)
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'LIST_APPEND',
line=1, end_line=2, column=1, end_column=8, occurrence=1)
line=1, end_line=2, column=5, end_column=8, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=1, end_line=2, column=1, end_column=8, occurrence=1)
line=1, end_line=2, column=5, end_column=8, occurrence=1)

def test_multiline_async_list_comprehension(self):
snippet = textwrap.dedent("""\
async def f():
[(x,
_ = [(x,
2*x)
async for x
in [1,2,3] if (x > 0
Expand All @@ -2093,11 +2093,11 @@ async def f():
compiled_code = g['f'].__code__
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'LIST_APPEND',
line=2, end_line=3, column=5, end_column=12, occurrence=1)
line=2, end_line=3, column=9, end_column=12, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=2, end_line=3, column=5, end_column=12, occurrence=1)
line=2, end_line=3, column=9, end_column=12, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
line=2, end_line=7, column=4, end_column=36, occurrence=1)
line=2, end_line=2, column=4, end_column=5, occurrence=1)

def test_multiline_set_comprehension(self):
snippet = textwrap.dedent("""\
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_compiler_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,39 @@ def test_frozenset_optimization(self):
('RETURN_VALUE', None)
]
self.codegen_test(snippet, expected)

def test_comp_without_target_optimization(self):
snippet = "[i for i in range(10)]"
expected = [
('RESUME', 0),
('ANNOTATIONS_PLACEHOLDER', None),
('LOAD_NAME', 0),
('PUSH_NULL', None),
('LOAD_CONST', 0),
('CALL', 1),
('LOAD_FAST_AND_CLEAR', 0),
('SWAP', 2),
('SETUP_FINALLY', 20),
('COPY', 1),
('GET_ITER', 0),
('FOR_ITER', 16),
('STORE_FAST', 0),
('LOAD_FAST', 0),
('POP_TOP', None),
('JUMP', 11),
('END_FOR', None),
('POP_ITER', None),
('POP_BLOCK', None),
('JUMP_NO_INTERRUPT', 25),
('SWAP', 2),
('POP_TOP', None),
('SWAP', 2),
('STORE_FAST_MAYBE_NULL', 0),
('RERAISE', 0),
('SWAP', 2),
('STORE_FAST_MAYBE_NULL', 0),
('POP_TOP', None),
('LOAD_CONST', 1),
('RETURN_VALUE', None),
]
self.codegen_test(snippet, expected)
8 changes: 8 additions & 0 deletions Lib/test/test_dictcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ def iter_raises():
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
expected)

def test_hash_error(self):
class Unhashable:
def __hash__(self):
0/0

with self.assertRaises(ZeroDivisionError):
{unhashable: 1 for unhashable in [Unhashable()]}


if __name__ == "__main__":
unittest.main()
42 changes: 42 additions & 0 deletions Lib/test/test_listcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,48 @@ def iter_raises():
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
expected)

def test_optimization_with_side_effects(self):
# List comprehensions that aren't used as a value are optimized
# to avoid creating a list. Ensure that side effects are still
# retained when this happens.
with self.assertRaises(ZeroDivisionError):
[0/0 for _ in [1]]

count = 0
def increment():
nonlocal count
count += 1

[increment() for _ in range(5)]
self.assertEqual(count, 5)

def test_async_optimization_with_side_effects(self):
async def gen1(aiterator):
with self.assertRaises(ZeroDivisionError):
[0/0 async for _ in aiterator]

async def gen2(aiterator):
[increment() async for _ in aiterator]

async def numbers():
for i in range(5):
yield i

count = 0
def increment():
nonlocal count
count += 1

def exhaust(coro):
try:
coro.send(None)
except StopIteration:
pass

exhaust(gen1(numbers()))
exhaust(gen2(numbers()))
self.assertEqual(count, 5)

__test__ = {'doctests' : doctests}

def load_tests(loader, tests, pattern):
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_setcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ def iter_raises():
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
expected)

def test_hash_error(self):
class Unhashable:
def __hash__(self):
0/0

with self.assertRaises(ZeroDivisionError):
{unhashable for unhashable in [Unhashable()]}


if __name__ == "__main__":
unittest.main()

__test__ = {'doctests' : doctests}

def load_tests(loader, tests, pattern):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid creating :class:`list` objects in comprehensions when the comprehension
is not used as a value.
Loading
Loading