1. Variables bind names to objects, not memory addresses
A Python variable is a label attached to an object. Assigning `x = 5` does not reserve a memory slot called `x`. Python creates the integer object `5`, then points the name `x` at it. This explains a behavior that confuses every student in week 2: assigning `y = x` and then `x = 10` leaves `y` still pointing at `5`. The integer object never changed; only the name `x` got re-pointed.
This model has three practical consequences. Reassignment is cheap because nothing copies. Type changes mid-script are legal: `x = 5` then `x = "hello"` runs without error. And mutable objects shared by two names update for both names, which is the source of the most common pandas and NumPy bugs later on.
**Naming rules** that Gradescope checks: lowercase with underscores (`student_score`), no leading digit, no Python keywords (`class`, `lambda`, `if`). Single underscores are convention for "throwaway" (`_, value = pair`). Double-leading-underscore triggers name mangling in classes and is reserved for OOP work.
# Run with: python variables.py
# Demonstrates name binding, not memory copying
x = 5 # name "x" points to int object 5
y = x # name "y" points to the SAME object 5
print(f"x={x}, y={y}, same object: {x is y}") # True - both names, one object
x = 10 # "x" is now repointed to a new int object 10
print(f"x={x}, y={y}, same object: {x is y}") # False - y still on 5
# Mutable case: this is where bugs hide
list_a = [1, 2, 3] # name "list_a" points to a list object
list_b = list_a # "list_b" points to the SAME list
list_b.append(99) # mutates the shared object in place
print(f"list_a={list_a}") # [1, 2, 3, 99] - surprise mutation
print(f"list_b={list_b}") # [1, 2, 3, 99]