Bug report
Bug description:
Since gh-126835 (moving constant folding from AST to CFG), set and list literals with more than 30 constant elements are no longer constant-folded in in/not in membership tests and for loop iteration. This causes the collection to be rebuilt from scratch on every evaluation instead of being stored as a compile-time constant.
For example:
import dis # 30 elements: folded to frozenset constant dis.dis("x in {%s}" % ", ".join(str(i) for i in range(1, 31))) # LOAD_NAME x # LOAD_CONST frozenset({1, 2, ..., 30}) # CONTAINS_OP # 31 elements: NOT folded, set rebuilt every time (BAD) dis.dis("x in {%s}" % ", ".join(str(i) for i in range(1, 32))) # LOAD_NAME x # BUILD_SET 0 # LOAD_SMALL_INT 1 # SET_ADD 1 # ... (31 x LOAD + SET_ADD) # CONTAINS_OP
Using a set-membership check with a set literal with 50 elements as a microbenchmark, this manifests as a ~20x slowdown from 3.13 to 3.14:
import timeit, sys elts = ", ".join(str(i) for i in range(50)) t = timeit.timeit(f"25 in {{{elts}}}", number=1_000_000) print(f"25 in {{0..49}}: {t:.3f}s")
Python 3.13.13: 25 in {0..49}: 0.015s
Python 3.14.4: 25 in {0..49}: 0.291s
It also affects code object size:
for n in [30, 31, 50, 100]: elts = ", ".join(str(i) for i in range(1, n + 1)) code = compile(f"x in {{{elts}}}", "<test>", "eval") print(f"x in {{1..{n}}}: {len(code.co_code):>5} bytes, {len(code.co_consts)} const(s)")
Python 3.13.13:
x in {1..30}: 12 bytes, 1 const(s)
x in {1..31}: 12 bytes, 1 const(s)
x in {1..50}: 12 bytes, 1 const(s)
x in {1..100}: 12 bytes, 1 const(s)
Python 3.14.4:
x in {1..30}: 12 bytes, 2 const(s)
x in {1..31}: 136 bytes, 1 const(s)
x in {1..50}: 212 bytes, 1 const(s)
x in {1..100}: 412 bytes, 1 const(s)
CPython versions tested on:
3.15, CPython main branch, 3.14
Operating systems tested on:
macOS, Linux