diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 9edbca3c383b43d..7608d147ed20c69 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1119,6 +1119,160 @@ def or_false(x): self.assertIn('LOAD_', opcodes[-2].opname) self.assertEqual('RETURN_VALUE', opcodes[-1].opname) + def test_empty_set_unpack_literal_bytecode_optimization(self): + cases = { + # optimized cases + '{*()}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('RETURN_VALUE', None), + ], + '{*(), *()}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('RETURN_VALUE', None), + ], + '{*(), 1}': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_SET', 1), + ('RETURN_VALUE', None), + ], + '{*(), 1, 2, 3}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', frozenset({1, 2, 3})), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + '{1, *()}': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_SET', 1), + ('RETURN_VALUE', None), + ], + '{1, 2, 3, *()}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', frozenset({1, 2, 3})), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + '{1, 2, *(), 3}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', frozenset({1, 2, 3})), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + # unoptimized cases + '{*(1,)}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_CONST', (1,)), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + '{*(x,)}': [ + ('RESUME', 0), + ('BUILD_SET', 0), + ('LOAD_NAME', 'x'), + ('BUILD_TUPLE', 1), + ('SET_UPDATE', 1), + ('RETURN_VALUE', None), + ], + } + + for source, expected in cases.items(): + with self.subTest(source=source): + code = compile(source, '', 'eval') + instructions = [ + (instruction.opname, instruction.argval) + for instruction in dis.get_instructions(code) + ] + self.assertEqual(instructions, expected) + + def test_empty_leading_tuple_unpack_list_and_tuple_bytecode_optimization(self): + cases = { + # optimized cases + '[*()]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('RETURN_VALUE', None), + ], + '[*(), *()]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('RETURN_VALUE', None), + ], + '[*(), 1]': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_LIST', 1), + ('RETURN_VALUE', None), + ], + '(*(),)': [ + ('RESUME', 0), + ('LOAD_COMMON_CONSTANT', ()), + ('RETURN_VALUE', None), + ], + '(*(), *())': [ + ('RESUME', 0), + ('LOAD_COMMON_CONSTANT', ()), + ('RETURN_VALUE', None), + ], + '(*(), 1)': [ + ('RESUME', 0), + ('LOAD_CONST', (1,)), + ('RETURN_VALUE', None), + ], + '[1, *()]': [ + ('RESUME', 0), + ('LOAD_SMALL_INT', 1), + ('BUILD_LIST', 1), + ('RETURN_VALUE', None), + ], + '[1, 2, 3, *()]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_CONST', (1, 2, 3)), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + '[1, 2, *(), 3]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_CONST', (1, 2, 3)), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + # unoptimized cases + '[*(1,)]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_CONST', (1,)), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + '[*(x,)]': [ + ('RESUME', 0), + ('BUILD_LIST', 0), + ('LOAD_NAME', 'x'), + ('BUILD_TUPLE', 1), + ('LIST_EXTEND', 1), + ('RETURN_VALUE', None), + ], + } + + for source, expected in cases.items(): + with self.subTest(source=source): + code = compile(source, '', 'eval') + instructions = [ + (instruction.opname, instruction.argval) + for instruction in dis.get_instructions(code) + ] + self.assertEqual(instructions, expected) + def test_imported_load_method(self): sources = [ """\ diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst new file mode 100644 index 000000000000000..35c2e562cf60835 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst @@ -0,0 +1 @@ +Optimize bytecode for empty unpack cases such as ``{*()}``. diff --git a/Python/codegen.c b/Python/codegen.c index 205c49cff1827c4..5855a8de31c1c14 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3412,24 +3412,46 @@ codegen_boolop(compiler *c, expr_ty e) return SUCCESS; } +static bool +is_empty_starred_literal(expr_ty elt) +{ + if (elt->kind != Starred_kind) { + return false; + } + expr_ty value = elt->v.Starred.value; + return (value->kind == Tuple_kind && + asdl_seq_LEN(value->v.Tuple.elts) == 0) || + (value->kind == List_kind && + asdl_seq_LEN(value->v.List.elts) == 0) || + (value->kind == Dict_kind && + asdl_seq_LEN(value->v.Dict.keys) == 0); +} + static int starunpack_helper_impl(compiler *c, location loc, asdl_expr_seq *elts, PyObject *injected_arg, int pushed, int build, int add, int extend, int tuple) { - Py_ssize_t n = asdl_seq_LEN(elts); - int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE; + Py_ssize_t end = asdl_seq_LEN(elts); + Py_ssize_t n = 0; int seen_star = 0; - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 0; i < end; i++) { expr_ty elt = asdl_seq_GET(elts, i); if (elt->kind == Starred_kind) { + if (is_empty_starred_literal(elt)) { + continue; + } seen_star = 1; - break; } + n++; } + int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE; if (!seen_star && !big) { - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 0; i < end; i++) { expr_ty elt = asdl_seq_GET(elts, i); + if (is_empty_starred_literal(elt)) { + continue; + } VISIT(c, expr, elt); } if (injected_arg) { @@ -3444,15 +3466,20 @@ starunpack_helper_impl(compiler *c, location loc, return SUCCESS; } int sequence_built = 0; + Py_ssize_t nitems = 0; if (big) { ADDOP_I(c, loc, build, pushed); sequence_built = 1; } - for (Py_ssize_t i = 0; i < n; i++) { + for (Py_ssize_t i = 0; i < end; i++) { expr_ty elt = asdl_seq_GET(elts, i); + if (elt->kind == Starred_kind) { + if (is_empty_starred_literal(elt)) { + continue; + } if (sequence_built == 0) { - ADDOP_I(c, loc, build, i+pushed); + ADDOP_I(c, loc, build, nitems+pushed); sequence_built = 1; } VISIT(c, expr, elt->v.Starred.value); @@ -3464,6 +3491,7 @@ starunpack_helper_impl(compiler *c, location loc, ADDOP_I(c, loc, add, 1); } } + nitems++; } assert(sequence_built); if (injected_arg) {