What Happens Between Typing python script.py and Seeing Output
CPython does not run your source code directly -- it compiles it to bytecode first and then executes that bytecode in an interpreter loop.
When you run python script.py, CPython, the reference implementation most people mean when they say Python, does not execute your source text directly. It first reads the file and runs it through a tokenizer and parser, turning your code into an abstract syntax tree that represents the structure of your program, function definitions, expressions, loops, independent of the exact characters you typed.
That AST is then compiled into bytecode: a lower-level, platform-independent instruction set specific to the Python interpreter, made up of opcodes like LOAD_FAST, CALL, and RETURN_VALUE. This is a real compilation step, distinct from the machine-code compilation you would get from a C or Rust compiler; Python bytecode is not something your CPU can execute directly, it is something the Python virtual machine executes.
For files that get imported as modules, CPython caches this compiled bytecode on disk in a __pycache__ directory as a .pyc file, keyed to the source file's modification time, so that re-running the same code without changes skips re-parsing and re-compiling it. Your top-level script itself is typically compiled fresh each run rather than cached, since it is the entry point rather than an imported module.
The actual execution happens in the CPython interpreter's evaluation loop, a large C function that reads bytecode instructions one at a time and carries out whatever each one specifies, pushing and popping values on a stack, looking up names, calling functions, against Python's C-level data structures. This is why Python is generally described as an interpreted language even though a real compilation step happens: there is no separate step that hands off to your operating system to run a standalone binary. The interpreter keeps running that same loop, dispatching one bytecode instruction after another, for as long as your program has instructions left, which is also why the process stays alive as the Python interpreter rather than exiting to a compiled artifact the way a C program would.
