CPython Runtime, AST, Bytecode & Frame Execution
Understanding how Python source code executes requires stepping into the CPython C Runtime Engine. From text tokenization and Abstract Syntax Tree (AST) parsing to compilation into bytecode (PyCodeObject), execution frame allocation (PyFrameObject), and evaluation inside ceval.c (_PyEval_EvalFrameDefault), this chapter unveils CPythonβs execution mechanics.
This chapter details CPython compilation phases, PyCodeObject struct contents, PyFrameObject stack frame layouts, and Python 3.11+ Specializing Adaptive Interpreter (PEP 659) opcode acceleration.
1. The CPython Compilation & Execution Pipeline
Executing python app.py triggers an explicit 4-phase transformation:
CPython Compilation & Execution Pipeline:
[ Source Code (.py) ]
|
v (Tokenizer & Parser)
[ Concrete / Abstract Syntax Tree (AST) ]
|
v (Compiler)
[ Bytecode (PyCodeObject saved to __pycache__/*.pyc) ]
|
v (Interpreter Engine: ceval.c)
[ Execution Frame (PyFrameObject) ] ββ> [ CPU Execution Loop ]- Tokenizer & Parser: Scans raw source characters, emitting a stream of tokens that build an Abstract Syntax Tree (AST).
- Compiler: Translates AST nodes into a
PyCodeObjectcontaining bytecode instruction bytes, variable arrays, and constant tables. - Frame Evaluator (
ceval.c): Allocates aPyFrameObjecton the C stack and passes it to_PyEval_EvalFrameDefault(), which executes opcodes in a giant switch/computed-goto loop.
2. Code Objects (PyCodeObject) vs. Execution Frames (PyFrameObject)
PyCodeObject(Static Read-Only Blueprint): Immutable struct compiled once per function. Contains raw bytecode bytes (co_code), constant tuples (co_consts), variable name tuples (co_varnames,co_names), and stack depth bounds (co_stacksize).PyFrameObject(Dynamic State Instance): Heap/stack allocated struct instantiated every time a function is called. Manages dynamic execution state:
PyFrameObject Memory Layout:
[ PyFrameObject ]
βββ f_code: Pointer to PyCodeObject
βββ f_globals: Module globals dict
βββ f_localsplus: C array of Fast Local Variables (LOAD_FAST)
βββ f_valuestack: Pointer to current top of Evaluation Value Stack3. Python 3.11+ Specializing Adaptive Interpreter (PEP 659)
Python 3.11 introduced a massive performance engine: the Specializing Adaptive Interpreter:
- Inline Caching: Generic opcodes (like
BINARY_SUBSCRorLOAD_ATTR) inspect runtime operand types over repeated executions. - Opcode Specialization: If a
LOAD_ATTRoperation repeatedly accesses an attribute on a specific class, CPython rewrites the bytecode opcode in-place to a specialized variant (LOAD_ATTR_MODULEorLOAD_ATTR_SLOT), bypassing generic dictionary lookups and executing 10x to 100x faster!
Adaptive Opcode State Machine:
[ Generic Opcode (e.g. LOAD_ATTR) ] ββ(Executed 8 times)ββ> [ Warm State ]
|
(Type is consistent)
v
[ Optimized Specialized Opcode (LOAD_ATTR_SLOT) ] <ββββββ [ Specialized State ]4. Production Trade-offs & Bytecode Inspection
- Disassembling Bytecode (
dis): Use Pythonβs built-indismodule to inspect bytecode instructions (dis.dis(func)) and verify opcode counts when tuning performance-critical loops.