← all cheat sheets
FUNDAMENTALS · MECHANISM WALKTHROUGH

Running a Python Script
What Really Happens, Step by Step

from typing `python script.py` to the process exiting — parsing, bytecode, and the import system.
python script.py INTERPRETER STARTS PARSE / TOKENIZE COMPILE TO BYTECODE IMPORTS RESOLVED PVM EXECUTES EXIT CODE
01 The Full Sequence — Worked Example
1

Shell Finds the Interpreter

Running python script.py first goes through normal shell PATH resolution to find the python executable — this is why an inactive virtualenv silently uses the wrong interpreter.

Resolve
2

CPython Process Starts

The interpreter process launches, initializes the core runtime (memory allocator, built-in types, sys module), and sets up sys.path — the list of directories Python will search for imports.

Init
3

Lexing & Parsing

script.py's source text is tokenized (keywords, identifiers, operators) and parsed into an Abstract Syntax Tree — a structural representation of the code, independent of formatting or comments.

Parse
4

Compile AST to Bytecode

The AST is compiled into Python bytecode — a lower-level, portable instruction set (not machine code). This is the same bytecode format used whether the script runs on Linux, macOS, or Windows.

Compile
5

Checkpoint — Nothing Has Actually Run Yet

Up to this point it's pure translation: text → tokens → AST → bytecode. No line of your code's logic has executed. Execution only begins in the next step, inside the Python Virtual Machine.

Checkpoint
6

import Statements Trigger the Import System

When execution hits an import requests line, Python checks sys.modules (already-imported cache) first, then searches sys.path in order — this is exactly why the wrong virtualenv silently imports the wrong (or a missing) package.

Import
7

Module Bytecode Cached (.pyc)

For imported modules (not the entry script itself), CPython writes compiled bytecode to __pycache__/*.pyc, keyed by source hash/timestamp — so re-running later skips steps 3-4 for unchanged modules, speeding up subsequent runs.

Cache
8

Python Virtual Machine Executes Bytecode

The PVM is a stack-based interpreter loop — it reads bytecode instructions one at a time (LOAD_NAME, CALL_FUNCTION, etc.) and executes them against a stack of Python objects. This loop is where your actual program logic runs.

Execute
9

The GIL Serializes Bytecode Execution

In standard CPython, the Global Interpreter Lock ensures only one thread executes Python bytecode at a time, even on a multi-core machine. Threads can still overlap for I/O-bound work, but pure CPU-bound Python code doesn't parallelize across threads because of this.

GIL
10

Frames & the Call Stack

Every function call pushes a new frame onto the interpreter's call stack, holding local variables and the current bytecode position. This is what a Python traceback is literally printing when a script errors out.

Frames
11

Script Ends — Exit Code Returned

When the script finishes (or raises an uncaught exception), the process exits with a code: 0 for clean completion, non-zero on an unhandled exception — this exit code is exactly what a shell script or CI pipeline checks to decide success/failure.

Exit
02 How to Explain This in an Interview
03 Follow-Up / Gotcha Questions
Q Why does "python script.py" sometimes silently use the wrong interpreter?
A PATH resolution happens before Python ever runs — if a virtualenv isn't activated, the shell finds a different (often system) python executable first, with its own separate sys.path and installed packages.
Q Does the GIL mean Python can't do anything concurrently?
A No — I/O-bound work (network calls, file reads) releases the GIL while waiting, so threads still help there. It's specifically CPU-bound pure-Python code that doesn't parallelize across threads; multiprocessing sidesteps the GIL entirely by using separate processes.
Q Why doesn't the entry script itself get a .pyc cache file?
A The script you run directly is compiled fresh every invocation since it's the __main__ module; only imported modules benefit from the __pycache__ bytecode cache.
Q What happens if two modules in sys.path have the same name?
A Whichever directory appears first in sys.path wins — this is a common source of accidentally shadowing a standard library or installed package with a same-named local file.
Q If a script raises an uncaught exception, what exit code does it return?
A 1, by CPython convention, unless the exception handler or sys.exit() explicitly sets something else — enough for a shell script's $? or CI step to detect failure without parsing output.
Q Is Python bytecode the same as machine code?
A No — bytecode is a portable, Python-specific instruction set interpreted by the PVM at runtime. It's not compiled to native machine instructions the way C or Go is, which is a core reason for CPython's relative slowness on CPU-bound work.
04 Quick-Fire Glossary
TermMeaning
CPythonThe standard, reference implementation of Python most people mean by "Python"
ASTAbstract Syntax Tree — structural representation of parsed source code
BytecodePortable, low-level instructions compiled from the AST, run by the PVM
PVMPython Virtual Machine — the stack-based loop that executes bytecode
sys.pathOrdered list of directories searched when resolving an import
sys.modulesCache of already-imported modules, checked before re-importing
GILGlobal Interpreter Lock — serializes bytecode execution to one thread at a time in CPython
__pycache__Directory holding cached .pyc bytecode for imported modules
FramePer-call-stack record of local variables and bytecode position
Exit CodeInteger the process returns to the OS/shell on completion — 0 means success