Compile
bump.py is plain text. CPython, the standard Python, does not run that text directly. It first translates it into bytecode: short, simple instructions for a small machine built in software, the virtual machine (VM).
Before running line 1, the compiler reads the whole file. It checks the grammar, builds a tree of the code, then writes bytecode for it. A typo stops the program here, before anything runs.
Each function body becomes a code object. It holds the bytecode, the local names and the constants. The dis module prints the bytecode: seven instructions for bump's one line.
Run
Line 3 calls bump(4). The VM sets up a frame: a workspace for this one call, with a slot for each local name. 4 goes into the slot for n. The VM starts at instruction 1, RESUME, which only marks the start.
The VM works with a value stack: a pile where the newest value sits on top. LOAD_FAST n copies a reference to n's value, 4, onto the stack.
LOAD_CONST 2 pushes the constant 2, which the compiler stored in the code object. Now the stack holds two values, 4 under 2. The compiler worked out that bump never needs more than two.
BINARY_OP * pops the top two values, 4 and 2, multiplies them and pushes the answer, 8. One instruction, one small job: that is how the VM runs every Python line.
LOAD_CONST 1 pushes the next constant. The stack now holds 8 under 1, ready for the addition.
BINARY_OP + pops 8 and 1, adds them and pushes 9. The same instruction handles every binary operator; its argument says which one.
RETURN_VALUE pops 9 and returns it to the caller. The frame is finished and thrown away, local n with it. Back on line 3, print() shows 9.
Speed up
Since Python 3.11 the VM adapts. Once bump has run many times with whole numbers, it swaps in specialized instructions made for those types, such as BINARY_OP_ADD_INT, which run faster. Your code never changes.
Try it yourself
;; step 1/11
# bump.py
def bump(n):
return n * 2 + 1
print(bump(4))
$ python bump.py
9
The bytecode shown is CPython 3.13's. Bytecode is an implementation detail and changes between versions: CPython 3.14 prints LOAD_FAST_BORROW for the first load and LOAD_SMALL_INT for the two constants. The card shows each instruction's name and what its argument means; dis also prints the raw argument number. When you import a module, CPython saves its compiled bytecode in a __pycache__ folder as a .pyc file, so the next import can skip the compiler. The file you run directly is compiled every time and not cached.