This mental model explains aliasing bugs, default-argument surprises and how function arguments behave.
In Python, assignment binds a name to an object; it never copies. After b = a, both names refer to the same object. Whether that matters depends on mutability.
- Immutable:
int,float,str,tuple,frozenset,bytes. Operations create new objects, so sharing is harmless. - Mutable:
list,dict,set, most user classes. Changing the object in place is visible through every name that refers to it.
So b = a; b.append(4) changes a too, while b = a; b = b + [4] rebinds b to a new list and leaves a alone. id(x) returns an object's identity, and a is b asks whether two names point at the same object.
Function arguments follow the same rule ("call by object reference"): the parameter is a new name for the caller's object. A function that mutates a list argument changes the caller's list; a function that rebinds its parameter does not affect the caller.
To get an independent copy, ask for one:
items.copy(),list(items)oritems[:]make a shallow copy — a new outer list holding the same inner objectscopy.deepcopy(obj)recursively copies nested structures
The classic trap is grid = [[0] * 3] * 3: the outer * repeats the reference to one inner list three times, so setting grid[0][0] changes every row. Build it with a comprehension instead: [[0] * 3 for _ in range(3)].
Example
import copy
a = [1, 2, 3]
b = a # same object
b.append(4)
print(a, a is b) # [1, 2, 3, 4] True
c = a.copy() # shallow copy
c.append(5)
print(a, c) # [1, 2, 3, 4] [1, 2, 3, 4, 5]
def add_item(lst):
lst.append("x") # mutates the caller's list
add_item(a)
print(a) # [1, 2, 3, 4, 'x']
bad = [[0] * 2] * 2
bad[0][0] = 9
print(bad) # [[9, 0], [9, 0]]
good = [[0] * 2 for _ in range(2)]
good[0][0] = 9
print(good) # [[9, 0], [0, 0]]
nested = {"tags": ["a"]}
deep = copy.deepcopy(nested)
deep["tags"].append("b")
print(nested) # {'tags': ['a']}Remember
- Assignment binds names; it never copies
- Mutating an object is visible through every name bound to it
- Use
.copy()for a shallow copy andcopy.deepcopyfor nested data
Watch out for
- Creating a 2-D list with
[[0] * n] * m, which aliases every row.