https://github.com/python/cpython/commit/f8b0e26a6ac72beb5db8cd8c22ebfd6e93349a6a
commit: f8b0e26a6ac72beb5db8cd8c22ebfd6e93349a6a
branch: main
author: Peter Gessler <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-09-20T21:53:10-07:00
summary:

gh-150737: Optimize bytecode for empty unpack cases such as ``{*()}``. (#150812)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-06-02-16-21-02.gh-issue-150737.LoYUFY.rst
M Lib/test/test_compile.py
M Python/codegen.c

diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 959732fc6e4a83d..553ac70d83a802c 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -1145,6 +1145,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, '<test>', '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, '<test>', '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 c12baf6b15a6dec..c4b749a539efdd4 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -3454,24 +3454,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) {
@@ -3486,15 +3508,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);
@@ -3506,6 +3533,7 @@ starunpack_helper_impl(compiler *c, location loc,
                 ADDOP_I(c, loc, add, 1);
             }
         }
+        nitems++;
     }
     assert(sequence_built);
     if (injected_arg) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to