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]