The Secret Life of Python: The Matryoshka Trap
Source: Dev.to
Timothy’s Nested Plan
Timothy was humming a tune as he organized the library’s archives. He felt invincible, having mastered the “slice” ([:]) syntax that allowed him to clone lists and avoid the curse of aliasing.
“Margaret, I’m reorganizing the library sections. I made a master map of the shelves and a backup before I move anything around. Look at this beautiful shallow copy.”
He displayed his code, confident in his new skills.
# Timothy's Nested Plan
shelf_A = ["Fiction", "Mystery"]
shelf_B = ["Biography", "History"]
# The Master Layout: a list of lists
library_layout = [shelf_A, shelf_B]
# The Backup: using the slice trick
backup_layout = library_layout[:] # (Surely this clones everything... right?)
# Move "Mystery" to a new location in the backup
backup_layout[0].remove("Mystery")
print(f"Backup Shelf A: {backup_layout[0]}")
print(f"Master Shelf A: {library_layout[0]}")
Console output
Backup Shelf A: ['Fiction']
Master Shelf A: ['Fiction']
Timothy was stunned. The “Mystery” book had disappeared from the master layout as well, even though he thought the slice created a completely independent list.
The Shallow Copy Trap
Margaret explained that a slice (or list.copy()) creates a new outer container but shares references to any mutable objects inside it.
Original List Backup List
[Box 1] [Box 2]
| |
+-------+ +-------+
| |
v v
[Inner List]
(Shared Data!)
When Timothy modified backup_layout[0], he was actually modifying the same inner list that library_layout[0] points to. This is the classic Matryoshka (nested‑doll) trap: a shallow copy only copies the first layer.
Deep Copy Solution
To obtain a completely independent copy—including all nested mutable objects—Timothy needed a deep copy.
import copy
shelf_A = ["Fiction", "Mystery"]
shelf_B = ["Biography", "History"]
library_layout = [shelf_A, shelf_B]
# Deep copy: recursively copies everything
backup_layout = copy.deepcopy(library_layout)
backup_layout[0].remove("Mystery")
print(f"Backup Shelf A: {backup_layout[0]}")
print(f"Master Shelf A: {library_layout[0]}")
Output
Backup Shelf A: ['Fiction']
Master Shelf A: ['Fiction', 'Mystery']
copy.deepcopy() walks the entire object graph, cloning each mutable element it encounters, so changes to the backup never affect the original.
Performance Considerations
-
Why isn’t deep copy the default?
Deep copying is expensive in both time and memory. For small or flat structures the overhead is negligible, but cloning large, deeply nested structures repeatedly can become a performance bottleneck. -
Python’s default philosophy
Python favors speed; shallow copies are cheap and sufficient for many use‑cases. When safety for nested data is required, you must request it explicitly.
Guidelines for Copying
| Situation | Recommended Copy |
|---|---|
Flat list (e.g., [1, 2, 3]) | Shallow copy (list[:], list.copy(), or copy.copy(x)) |
Nested list/dict (e.g., [[1, 2], [3, 4]]) | Deep copy (copy.deepcopy(x)) |
| Need a generic shallow copy for any object | copy.copy(x) (works on lists, dicts, custom objects) |
| Need a completely independent clone of a complex structure | copy.deepcopy(x) |
Quick reference
import copy
# Shallow copy (first layer only)
shallow = original_list[:] # or original_list.copy()
shallow_generic = copy.copy(original) # works on any object
# Deep copy (full recursion)
deep = copy.deepcopy(original)
When to Use Deep Copy
- Nested mutable containers – lists of lists, dicts containing lists, objects containing other mutable objects.
- Isolation required – when modifications to the copy must never affect the original, even deep inside the structure.
- Testing or sandboxing – creating a safe sandboxed version of data for experiments.
For simple, flat data structures, a shallow copy remains the most efficient choice.
The story continues in the next episode, “The Loophole,” where modifying a list while iterating over it creates a chaotic, skipping timeline.