Show the difference between a shallow copy and a deep copy of a nested Python structure.
A shallow copy duplicates the outer container but shares the objects inside it. A deep copy duplicates everything, recursively.
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] - inner list shared
print(shallow is original) # False - the outer list is new
deep[0].append(77)
print(original) # [[1, 2, 99], [3, 4]] - untouched
print(deep) # [[1, 2, 77], [3, 4]]The shallow copy printed a modified original, and that is the whole point. The new outer list has its own slots, but slot zero still points at the same inner list, so appending to it through the copy is visible through the original.
The deep copy made the same append and the original did not change, because deepcopy walked the whole structure and built new inner lists too. The print shows the original still holding the 99 from the shallow experiment and the deep copy holding its own 77.
Two other ways to make a shallow copy that come up often. The slicing idiom items[:] and the constructor list(items) both produce a new list with the same elements. And the built-in methods dict.copy, set.copy and list.copy do the same thing for their types. All four are shallow.
Deepcopy has a cost worth mentioning: it walks the entire object graph, so it is slow for large structures, and it can fail on objects that cannot be copied, such as an open file handle or a database connection. That is why deep copying an object that holds a connection is a common bug — the copy ends up with a reference to a connection it does not own.