In Python, variables don’t hold values directly — they hold references (pointers) to objects in memory. This means copying isn’t always what we expect.
[1, 2]
[1, 2]
[3, 4]
[1, 2]
[3, 4]
[1, 2]
[3, 4]
Assignment (=) — Not a Copy at All
Assignment just creates another name pointing to the same object. No copying happens.
a = [1, 2, [3, 4]]
b = a # b points to the SAME object
b.append(5)
print(a) # [1, 2, [3, 4], 5] — a is affected too!
print(id(a) == id(b)) # True — same object in memory
Shallow Copy
Creates a new outer object, but the nested objects inside still share the same references.
import copy
a = [1, 2, [3, 4]]
b = copy.copy(a) # shallow copy
# Other ways to shallow copy:
# b = a[:] — slice notation
# b = list(a) — constructor
# b = a.copy() — list's built-in method
b.append(5)
print(a) # [1, 2, [3, 4]] — outer list is independent
b[2].append(99)
print(a) # [1, 2, [3, 4, 99]] — nested list is SHARED!
This is where shallow copies break. The top-level list is new, but a[2] and b[2] still point to the exact same [3, 4] list.
Deep Copy
Creates a completely independent copy — every nested object is recursively duplicated.
import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
b[2].append(99)
print(a) # [1, 2, [3, 4]] — completely unaffected
print(b) # [1, 2, [3, 4, 99]]
Verifying with id()
We can use id() to check if two variables point to the same object.
import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
print(id(a) == id(b)) # False — different outer lists
print(id(a[2]) == id(b[2])) # True — same nested list (shallow!)
print(id(a[2]) == id(c[2])) # False — different nested lists (deep!)
Quick Reference
| Operation | New outer object? | New nested objects? |
|---|---|---|
b = a | No | No |
b = a.copy() | Yes | No |
b = copy.deepcopy(a) | Yes | Yes |
In simple language, assignment just creates another label for the same box. Shallow copy gives us a new box but the items inside are still shared. Deep copy gives us a completely new box with brand new items — nothing is shared.