x = 3
y = 19.99
z = FalseThe values exist, but the code hides their purpose.
Learn with diagrams, code, systems and practical examples.
Store values with meaningful names and update them safely.
Variables let your program remember information so later steps can use it.
A variable name refers to a value. The equals sign assigns the value on the right to the name on the left. Choose names that communicate meaning rather than position.
Run this example first. Do not change several things at once; confirm the basic behavior, then experiment.
customer_name = "Maya"
item_count = 3
item_count = item_count + 1
print(customer_name, item_count) A variable name refers to a value. The equals sign assigns the value on the right to the name on the left. Choose names that communicate meaning rather than position.
Maya 4The example is intentionally small. Focus on the chapter’s main idea before adding extra syntax or framework code.
Reading x = x + 1 as a mathematical equation.
Read assignment from right to left: calculate x + 1 first, then store that new value under x.
These are deliberately small. If you can complete them without copying the example, you are ready to continue.
Prepared deep chapter · Core Python
Imagine you are building a tiny shopping program. It needs to remember a customer name, how many items are in the cart, and whether the order is ready. You could write those values directly everywhere, but the program would become difficult to read and difficult to change. A variable gives a useful name to a value so later code can use that name.
The most important beginner idea is simple: in Python, a variable is a name bound to an object. The equals sign does not mean “these two things are mathematically equal forever.” It means: evaluate the expression on the right, then bind the name on the left to the resulting object.
Here is a complete example:
customer_name = "Maya"
item_count = 3
print(customer_name)
print(item_count) Maya
3Python evaluates "Maya", then binds the name customer_name to that string object. It evaluates 3, then binds item_count to that integer object. Later, print(item_count) asks Python to look up the current object associated with the name.
Right side first. Name second. Think: “work out the value, then remember it under this name.”
Programs change state. A cart can gain an item, a status can move from pending to shipped, and a running total can increase. Python handles this by letting a name be assigned again.
item_count = 3
item_count = item_count + 1
print(item_count) 4The second line is often confusing if you read it like school algebra. Python does not ask whether item_count equals itself plus one. It first reads the current value of item_count, calculates 3 + 1, gets 4, then rebinds item_count to the integer object 4.
status = "pending"
print(status)
status = "paid"
print(status) pending
paidThe name stayed the same; the value it refers to changed. That is useful when a variable represents the current state of something.
Python will accept many legal names, but readable code needs more than legal syntax. Compare x = 3 with retry_count = 3. The second name tells another reader what the value means.
x = 3
y = 19.99
z = FalseThe values exist, but the code hides their purpose.
item_count = 3
cart_total = 19.99
is_paid = FalseThe names reveal the domain without a separate comment.
Python variable names can contain letters, digits, and underscores, but they cannot begin with a digit. Names are case-sensitive, so total and Total are different names. For ordinary variables, Python code usually follows snake_case: lowercase words separated by underscores.
Name the thing by what it means in the program: customer_name, retry_count, has_access. Avoid names that only describe the data type, such as string1 or number2.
If Python cannot find a binding for a name, it cannot guess what value you intended.
print(order_total) NameError: name 'order_total' is not definedThis error is often simpler than it first appears. Check whether the assignment runs before the use, whether you misspelled the name, and whether the value lives inside a function scope that is not available where you are trying to read it.
item_count = 3
print(items_count)items_count is a different name, so Python raises NameError.
item_count = 3
print(item_count)Use one clear spelling consistently.
Python supports multiple assignment:
customer_name, item_count = "Maya", 3
print(customer_name)
print(item_count) Maya
3The expressions on the right are evaluated before the target names are assigned. That is why this compact swap works:
left = "A"
right = "B"
left, right = right, left
print(left, right) B AThis is useful Python syntax, but do not compress assignments merely to look clever. Separate lines are often better when each value has a different meaning or deserves its own explanation.
This is the point where the simple “variable is a box” analogy begins to break down. Two names can refer to the same mutable object. A list is mutable, which means its contents can change after creation.
items = ["book", "pen"]
backup = items
backup.append("notebook")
print(items)
print(backup) ['book', 'pen', 'notebook']
['book', 'pen', 'notebook']Why did items change? Because backup = items did not create another list. It created another name pointing to the existing list. The official Python tutorial describes this directly: simple assignment never copies data.
items = ["book", "pen"]
backup = items.copy()
backup.append("notebook")
print(items)
print(backup) ['book', 'pen']
['book', 'pen', 'notebook']list.copy() creates a shallow copy. If the list contains other mutable objects, those nested objects can still be shared. Deep-copy behavior belongs in a later chapter; the beginner lesson here is simply that assignment and copying are different operations.
Python is dynamically typed. The object has a type; the variable name itself is not permanently declared as one type.
value = 10
print(type(value).__name__)
value = "ten"
print(type(value).__name__) int
strPython allows this, but frequent unrelated type changes can make code harder to understand. A name should usually keep one clear meaning. If customer_id begins as an integer identifier and later becomes a full customer dictionary, the name is no longer communicating a stable contract.
What will this print?
count = 2
old_count = count
count = count + 1
print(count)
print(old_count) It prints 3 and then 2. Integers are immutable objects. old_count = count binds both names to the integer 2. Reassigning count later points only that name at a different integer object; it does not mutate the integer 2 or rebind old_count.
Now compare that with the list aliasing example above. That contrast—rebinding a name versus mutating a shared object—is one of the most useful mental models you can build early in Python.
Guided state tracing
When a variables problem feels confusing, slow the program down. Read one line, write down what each name refers to, then decide whether the line rebinds a name or mutates an existing object. These six traces turn that habit into a repeatable debugging skill.
Trace what cart_total and saved_total refer to after each line.
cart_total = 12.50 cart_total → 12.5Python evaluates 12.50, then binds cart_total to that float object.
saved_total = cart_total cart_total → 12.5saved_total → 12.5Both names now refer to the same immutable float object.
cart_total = cart_total + 2.50 cart_total → 15.0saved_total → 12.5The expression creates a new float result and only cart_total is rebound.
Takeaway: Rebinding one name does not rebind another name that happened to point at the same immutable object.
Predict whether primary changes when backup appends an item.
primary = ["book", "pen"] primary → list AA mutable list object is created and primary refers to it.
backup = primary primary → list Abackup → list AAssignment adds another name for the same list; it does not copy the list.
backup.append("notebook") primary → list A = ["book", "pen", "notebook"]backup → list A = ["book", "pen", "notebook"]append mutates list A, so both names observe the changed object.
Takeaway: When aliases share a mutable object, mutation is visible through every name that still refers to that object.
Compare rebinding from + with in-place mutation from +=.
items = ["book"]; alias = items items → list Aalias → list ABoth names start on the same list.
items = items + ["pen"] items → list Balias → list A+ builds a new list and items is rebound to it; alias still points to the original list.
alias += ["ruler"] items → list B = ["book", "pen"]alias → list A = ["book", "ruler"]For a list, += normally mutates the existing list in place.
Takeaway: The operator matters because the state transition may be rebinding or mutation.
Track the outer dictionaries and the nested tags list.
order = {"status": "new", "tags": ["gift"]} order → dict Adict A.tags → list TThe dictionary contains a reference to a nested mutable list.
copied = order.copy() order → dict Acopied → dict Bdict A.tags → list Tdict B.tags → list Tdict.copy() creates a new outer dictionary, but nested mutable values are still shared.
copied["tags"].append("priority") order.tags → ["gift", "priority"]copied.tags → ["gift", "priority"]Appending mutates the shared nested list, so both dictionaries appear to change there.
Takeaway: Shallow copy is enough for independent top-level immutable values, but not for nested mutable structures.
Trace how Python evaluates values first and then binds the targets.
first, *middle, last = [10, 20, 30, 40] first → 10middle → [20, 30]last → 40The starred target collects the middle remainder into a new list.
first, last = last, first first → 40middle → [20, 30]last → 10Python evaluates the right-hand values before rebinding the two target names.
Takeaway: Multiple assignment is still ordinary binding; the compact syntax does not change the right-side-first rule.
Distinguish a name bound to None from a name that has no binding.
status = None status → NoneNone is a real singleton object. The name exists and is bound to it.
del status status → <unbound>del removes the current binding for the name.
print(status) NameErrorAfter the binding is removed, reading the name raises NameError.
Takeaway: Use None when “no value yet” is itself meaningful state; an unbound name is a different situation.
Do not guess from the final output first. Trace the bindings, identify mutation versus rebinding, and then predict the output from the state you have written down.
NameError.product_name, unit_price, and quantity. Print a sentence that uses all three values.retry_count at 0, add one twice using reassignment, and predict the final value before running the code.primary. Assign secondary = primary, append through secondary, and explain why primary changes. Then repeat with primary.copy().NameError, read the traceback, and fix the spelling or ordering mistake that caused it.This prepared chapter uses the current Python 3.14.7 documentation as its technical reference. The teaching language is intentionally simpler than the language reference, but the binding/copying model is not simplified into an incorrect “variables are boxes” rule.
Mini project · Put the model to work
You now know enough about names, rebinding, and mutable objects to build a tiny but realistic state model. The goal is not to write a complete shopping application. The goal is to practice choosing clear names, predicting each change, and noticing when assignment shares an object instead of copying it.
customer_name = "Maya"
item_count = 2
status = "pending"
items = ["book", "pen"] Pause before adding more code. Four names now point at four objects. The string and integer values are immutable. The list is mutable, so another name can accidentally share the same changing object.
snapshot = items
items.append("notebook") snapshot and items point to the same list. Appending through either name changes the one shared object.
snapshot = items.copy()
items.append("notebook") The outer list is now separate. This is sufficient here because the list contains strings, not nested mutable objects.
item_count = item_count + 1
status = "paid" These statements do not mutate the old integer or string. Python evaluates the right side, creates or reuses the resulting object, and binds the name to that result. The names now describe the current order state.
print(customer_name)
print(item_count)
print(status)
print(items)
print(snapshot) Maya
3
paid
['book', 'pen', 'notebook']
['book', 'pen']If your snapshot also contains "notebook", return to Step 2 and inspect whether you copied the list or only created another name for the same list.
status not change an earlier variable that happened to point to the old string?items.copy() sufficient here, but potentially insufficient for a list containing nested dictionaries?Add a discount_applied Boolean and a cart_total number. Predict the bindings before and after each reassignment. Then explain, in one sentence per line, whether the operation rebinds a name or mutates an object.