Python Constant Folding in AST Optimization
Python optimizes code execution speed and bytecode size by evaluating static expressions at compile time, a technique known as constant folding. In modern CPython, this optimization occurs directly on the Abstract Syntax Tree (AST) before bytecode generation. By recognizing operations involving only immutable literal values—such as arithmetic expressions, string operations, and sequence literals—the compiler computes the final value during compilation, replacing complex AST subtrees with a single constant node and reducing runtime computational overhead.
The Shift from Peephole to AST Optimization
Historically, CPython performed constant folding late in the
compilation process using a bytecode peephole optimizer. This approach
required generating bytecode instructions, analyzing instruction
sequences, and replacing sequences of LOAD_CONST and binary
operators with a single LOAD_CONST.
Starting in Python 3.7 and finalized in Python 3.8, constant folding
was migrated upstream into the AST optimization phase, implemented in
Python/ast_opt.c. Performing folding on the AST simplifies
compiler logic, preserves semantic meaning better, and eliminates the
overhead of emitting and subsequently rewriting bytecode
instructions.
How the AST Optimizer Operates
The AST optimization pass runs recursively over the syntax tree before the compiler creates the control flow graph or emits bytecode. When traversing nodes, the optimizer checks whether an expression consists entirely of constants.
- Node Traversal: The compiler traverses the AST using a visitor pattern, descending into expressions recursively to resolve nested sub-expressions first.
- Literal Detection: The compiler inspects operand
nodes. If the operands are instances of
Constantnodes, they are candidates for folding. - Evaluation: The compiler invokes the corresponding
CPython C-API functions to evaluate the operation. For example, a
BinOpnode with anAddoperator between two integer constants will invoke the equivalent ofPyNumber_Add. - Node Replacement: If the operation succeeds without
error, the parent node (e.g.,
BinOp) is replaced directly in the tree with a newConstantnode holding the resulting value.
Expressions Handled by Constant Folding
The AST optimizer folds several categories of static expressions:
Arithmetic and Logical Operations
Expressions involving basic numeric literals are computed immediately:
# Source
seconds = 60 * 60 * 24
# Bytecode equivalent after folding
seconds = 86400Unary operations such as -5, +10, or
not True are folded into their reduced literal values
(-5, 10, False).
String and Bytes Concatenation
Static string and byte concatenations are joined into single literals:
# Source
greeting = "Hello, " + "World!"
# Bytecode equivalent after folding
greeting = "Hello, World!"Container Literals and Membership Tests
Tuples containing only constants are constructed at compile time. In
addition, lookup patterns like x in [1, 2, 3] or
x in {1, 2, 3} are optimized. The compiler converts the
list or set into an immutable tuple or
frozenset constant:
# Source
if x in [1, 2, 3]:
pass
# Bytecode equivalent after folding
if x in (1, 2, 3): # Evaluated using a pre-built constant tuple
passSafeguards and Constraints
Constant folding is constrained by strict rules to ensure that compilation remains fast, safe, and behaviorally identical to runtime execution:
- Resource Limits: To prevent excessive memory
consumption and compiler denial-of-service vulnerabilities, the
optimizer restricts collection expansion and repetition. For instance,
an expression like
'a' * 10_000_000is rejected by the optimizer because the resulting literal exceeds safety thresholds (typically 4,096 elements). It is deferred to runtime instead. - Error Handling: If an operation produces an
exception at compile time—such as division by zero
(
1 / 0)—the compiler halts the folding attempt without raising a syntax error. The original AST nodes are left intact, allowing the standard runtime exception (ZeroDivisionError) to be raised when the code executes. - Floating-Point Semantics: CPython preserves
floating-point edge cases, such as distinguishing between positive zero
(
0.0) and negative zero (-0.0), ensuring that compile-time arithmetic matches IEEE 754 standards.
Verifying Constant Folding
The effect of AST constant folding can be verified using the standard
library's dis module:
import dis
def calculate():
return 1000 * 60
dis.dis(calculate)The resulting disassembly shows a single LOAD_CONST
instruction rather than arithmetic operations:
2 0 LOAD_CONST 1 (60000)
2 RETURN_VALUE
Because the AST optimizer folds the multiplication into
60000 before code generation, the final code object avoids
the BINARY_OP instruction entirely.