PYTHON / VARIABLES AND DATA TYPES
Variables, names, and assignment
Bind, rebind, unpack, and delete Python names with confidence, and predict the errors that come from reading a name that has no value.
What you will learn
- Create and rebind names with = without declaring a type first
- Assign several names at once with tuple unpacking and starred targets
- Predict NameError and UnboundLocalError from where a name is assigned
- Explain why the right-hand side runs before any target is bound
Understanding Variables, names, and assignment
In Python you never declare a variable; you assign to a name and the name starts existing. The statement price = 12 evaluates 12, then records an entry in the current namespace mapping the name price to that value. The name is a label written on a value, not a fixed-size box that holds it, which is why nothing about the name records a type. That also means a name can exist without ever being written in your source code, if it was created dynamically, and it can vanish again with del.
Because names carry no type, rebinding is unrestricted: count = 3 followed by count = "three" is legal, and the second statement simply repoints the label. Every assignment statement works in a fixed order: Python fully evaluates the expression on the right, then binds the targets on the left from left to right. That order is what makes a, b = b, a swap two names without a temporary variable, since the pair (b, a) is built before either target is touched. Augmented forms such as total += n are still assignments to total, so total must already be bound before the line runs.
Where an assignment appears decides which namespace the name lands in. An assignment at module level creates a module-level name; an assignment anywhere inside a function body makes that name local to the whole function, decided when the function is compiled rather than when the line executes. Reading a name that was never bound raises NameError, and reading a name that is local but not yet bound raises UnboundLocalError, which is the usual reason a function that reads a module-level counter suddenly stops working the moment you add an assignment to it.
count = 3
print(count)
count = "three"
print(count)
x = y = 0
x += 1
print(x, y)
a, b = 1, 2
a, b = b, a
print(a, b)
del count
try:
print(count)
except NameError as err:
print("NameError:", err)Assignment binds a name in a namespace to the result of the right-hand expression, and a name is only usable after it has been bound.
Worked examples
Unpacking one value into several names
Shows how a single assignment statement can bind several names, including a starred target that collects the leftovers.
first, *rest = [10, 20, 30, 40]
print(first, rest)
name, score = ("ada", 91)
print(name, score)
head, *middle, tail = "python"
print(head, middle, tail)Example explained
Line 1first, *rest = [...] binds first to the single leading item and rest to a list of everything else.
Line 2name, score = ("ada", 91) matches two targets against two items; a mismatch in count would raise ValueError.
Line 3The right-hand side only has to be iterable, so a string unpacks character by character.
Line 4A starred target is always given a list, even when it captures nothing.
Targets are bound left to right
Demonstrates that the right-hand side is evaluated first, then each target is bound in order, so an earlier target can affect a later one.
values = [10, 20, 30]
i = 0
i, values[i] = 1, 99
print(i, values)Example explained
Line 1The tuple (1, 99) is built first, using no target values at all.
Line 2The target i is bound next, so i becomes 1.
Line 3Only then is values[i] evaluated as a target, and i is already 1, so index 1 is written.
Line 4Reversing the two targets would write index 0 instead, which is why mixing names and subscripts in one statement is easy to misread.
Assignment decides that a name is local
Shows that a single assignment inside a function makes the name local for the entire function body, even on lines that run earlier.
counter = 5
def show():
print(counter)
def bump():
counter = counter + 1
return counter
show()
try:
bump()
except UnboundLocalError:
print("UnboundLocalError: counter is local to bump")Example explained
Line 1show() only reads counter, so Python looks outward and finds the module-level binding.
Line 2bump() assigns to counter, which marks the name local for the whole function at compile time.
Line 3counter + 1 then tries to read the local counter before it has any value, raising UnboundLocalError.
Line 4Adding global counter as the first line of bump would make the assignment target the module-level name instead.
Important notes
del name removes the binding only. The value can stay alive if any other name still refers to it, and a later assignment can bring the same name back.
Assignment is a statement, not an expression, so x = (y = 3) is a syntax error; the walrus operator := exists for the cases where an expression really must bind a name.
Common mistakes
Writing total += 1 before total has ever been assigned; because += reads the name first, this fails with NameError instead of starting the count at 1.
Assuming b = a keeps b in sync with a; after a = a + 1 the name b still points at the old value, so later calculations silently use stale data.
Adding an assignment to a function that previously only read a module-level name; the name becomes local for the whole body and every earlier read of it raises UnboundLocalError.
Try it yourself
Change, predict, then run
Bind three names a, b, c to 1, 2, 3, then rotate them in a single assignment statement so a holds 3, b holds 1, and c holds 2, and print all three. Then del b and print b inside a try block, catching NameError and printing the error message.
Open the Python workspaceCheck your understanding
A module defines total = 0, and a function body contains only the line total = total + 5, with no global statement. What happens when you call the function?
- It updates the module-level total to 5, because assignment targets the nearest existing name.
- It returns 5, because Python reads the module-level total and then stores a local copy.
- It raises UnboundLocalError, because the assignment makes total local for the entire function body.
- It raises NameError, because module-level names are invisible inside functions.
Show answer
Python decides at compile time that any name assigned in a function body is local to that function, so total + 5 reads the local total, which has no value yet, and raises UnboundLocalError. The second option is tempting because reading a global from a function normally works, but that only holds when the function never assigns to that name; the single assignment changes how every mention of total in the body is resolved.