Python Mutability
1. What is Mutability?
Mutability describes whether an object can be directly modified after it is created.
| Mutability | Meaning | Python Types |
|---|---|---|
| Immutable | Cannot be changed once created | int, float, str, tuple, bool, None |
| Mutable | Can be directly changed in place | list, set, dict |
2. Immutable Objects
When you "modify" an immutable object, Python actually creates a brand new object.
x = 1
y = x
x += 1 # creates a NEW object with value 2, stores it in x
print(x) # → 2
print(y) # → 1 (unchanged — y still points to the original object)
Changing
xdoes not affectybecausex += 1creates a new object, it doesn't modify the existing one.
3. Mutable Objects
Mutable objects can be modified directly. If two variables point to the same mutable object, changing one affects the other.
x = []
y = x # y and x point to the SAME list object
x.append(2)
print(x) # → [2]
print(y) # → [2] (also changed — same object!)
To check if two variables point to the same object, use the is keyword:
x is y # True → same object
x == y # True → same value (but could be different objects)
4. The is Keyword vs ==
| Operator | Checks |
|---|---|
== |
Whether values are equal |
is |
Whether they are the same object in memory |
a = [1, 2, 3]
b = [1, 2, 3]
a == b # → True (same value)
a is b # → False (different objects)
c = a
a is c # → True (same object)
5. The id() Function
Returns the memory address of an object. Same ID = same object.
a = [1, 2, 3]
b = a
print(id(a)) # e.g. 140234567890
print(id(b)) # same number → same object
b = [1, 2, 3]
print(id(b)) # different number → different object
⚠️ Small integers (like
0,1,2) may share the same ID due to Python memory optimizations. Don't rely onid()for small ints.
6. Mutable Objects as Function Arguments
When you pass a mutable object to a function, the parameter points to the same object. Changes inside the function affect the original.
def funk(lst, x):
lst.append(x)
a = []
funk(a, 2)
funk(a, 3)
print(a) # → [2, 3] — modified outside the function!
This is called modifying in place — you're working on the same object, not a copy.
This is powerful but can cause bugs if you're not expecting it.
7. Making Copies
Copy a list — using slice
a = [1, 2, 3]
b = a[:] # brand new list with same values
a.append(4)
print(a) # → [1, 2, 3, 4]
print(b) # → [1, 2, 3] (unaffected)
Copy a set or dictionary — using .copy()
s1 = {1, 2, 3}
s2 = s1.copy() # new set
d1 = {"a": 1}
d2 = d1.copy() # new dict
8. Shallow Copy vs Deep Copy
Shallow Copy
Copies the outer object only. Any mutable objects stored inside are still shared.
a = [[1, 2], [3, 4]]
b = a[:] # shallow copy — b is a new list...
c = a[0] # ...but a[0] and b[0] are the SAME inner list
c.append(99)
print(a) # → [[1, 2, 99], [3, 4]]
print(b) # → [[1, 2, 99], [3, 4]] (inner list changed in both!)
Deep Copy
Copies the outer object and all nested mutable objects recursively. Requires importing a module.
import copy
b = copy.deepcopy(a) # fully independent copy
| Shallow Copy | Deep Copy | |
|---|---|---|
| Outer object | New object | New object |
| Nested mutable objects | Shared (same reference) | Copied (independent) |
| How to do it | a[:] or .copy() |
copy.deepcopy(a) |
For most use cases, a shallow copy is sufficient. Use deep copy only when your structure contains nested mutable objects you need to isolate.
9. Mutable Objects Inside Immutable Types
An immutable type (e.g. tuple) can store mutable objects. The tuple itself can't be changed, but the mutable objects inside it can.
a = [1, 2]
b = [3, 4]
tup = (a, b)
a.append(99)
print(tup) # → ([1, 2, 99], [3, 4]) — inner list changed!
The tuple didn't change (same two list references), but the list it stores was mutated.
10. Modifying In Place vs Returning a New Object
| Instruction | What to do |
|---|---|
| "Modify the list in place" | Work directly on the passed object (don't copy it) |
| "Return a new list" | Make a copy, work on the copy, return it |
# Modify in place
def add_item(lst, x):
lst.append(x) # changes the original
# Return new object
def add_item_new(lst, x):
new_lst = lst[:] # copy
new_lst.append(x)
return new_lst # original untouched
Cheat Sheet
# Check if same object
x is y
# Check memory address
id(x)
# Copy a list
b = a[:]
# Copy a set or dict
s2 = s1.copy()
d2 = d1.copy()
# Deep copy (nested mutable objects)
import copy
b = copy.deepcopy(a)
# Immutable types (can't modify directly)
int, float, str, tuple, bool, None
# Mutable types (can modify directly)
list, set, dict