CPython & Bytecode
CPython compiles Python source to bytecode (.pyc files), then executes that bytecode in a stack-based virtual machine.
Think of CPython as a translator who first writes down the speech in shorthand (bytecode), then reads the shorthand aloud (VM execution). It's not improvising live — there's always a written step first.
Compilation happens automatically before execution. .pyc files are cached in __pycache__ keyed by Python version and source hash. The dis module lets you inspect bytecode. CPython is the reference implementation — PyPy, Jython, etc. are alternatives with different trade-offs.
The bytecode VM is stack-based, not register-based. Each frame object holds its own stack, local variables, and reference to the code object. Frame creation is a major overhead for function calls. dis.dis(func) shows you exactly what the interpreter executes. LOAD_FAST / STORE_FAST are the cheapest local variable operations; LOAD_GLOBAL is slower (dict lookup). Python 3.11+ introduced specialized adaptive bytecode that self-optimizes hot paths.
CPython first compiles source to bytecode — a lower-level representation stored in .pyc files. Then the bytecode runs in a stack-based VM. This is why Python is 'interpreted' but not line-by-line — it compiles first. You can inspect bytecode with dis.dis(). CPython is the default implementation; PyPy uses JIT compilation for significantly faster execution of CPU-bound code.
Python is NOT purely interpreted line-by-line. It compiles to bytecode first. 'Interpreted' means the bytecode VM runs at runtime, not that compilation doesn't happen.