Shallow vs deep copy
copy.copy() duplicates the outer container only — nested mutable objects are still shared references.
copy.deepcopy() recursively duplicates everything. Slicing (lst[:]) and list(lst) are also shallow
copies, which surprises people who expect them to fully isolate nested data.
import copy
original = {"name": "a", "tags": ["x", "y"]}
shallow = copy.copy(original)
deep = copy.deepcopy(original)
shallow["tags"].append("SHALLOW-MUTATED")
print("original:", original)
print("shallow: ", shallow, " <- same dict copied, but 'tags' list is the SAME object as original's")
print("deep: ", deep, " <- untouched, fully independent")
print()
print("shallow['tags'] is original['tags']:", shallow["tags"] is original["tags"])
print("deep['tags'] is original['tags']: ", deep["tags"] is original["tags"])
Custom classes can control this via __copy__/__deepcopy__:
class Box:
def __init__(self, items):
self.items = items
def __deepcopy__(self, memo):
print(" (custom __deepcopy__ called)")
return Box(copy.deepcopy(self.items, memo))
def __repr__(self):
return f"Box({self.items})"
b = Box([1, 2, 3])
b2 = copy.deepcopy(b)
print(b, b2, b.items is b2.items)