Why Does Modifying a Copy Sometimes Change the Original?
The Problem
We assume you already know how to write simple Python programs and understand basic syntax (if, for, functions, lists). Here, weβre not discussing how to use the language, but why it works the way it does.
Try to guess what this code prints:
numbers = [[1, 2], [3, 4]]
copy = numbers.copy()
copy[0].append(99)
print(numbers)
If you take the word copy literally, the answer seems obvious. We modified the copy. So the original should stay unchanged. Many people expect: [[1, 2], [3, 4]]
But Python prints: [[1, 2, 99], [3, 4]]
That seems rather strange. We modified the copy, yet the original changed too. Does that mean copy() is broken? Not at all. copy() is working exactly as intended. The confusion comes from the fact that Python supports more than one kind of copying. Weβre used to thinking of a copy as a completely independent duplicate. But copy() doesnβt work that way. It creates a new list. It does not copy the objects stored inside that list.
How Shallow Copy Works
Letβs see what actually happens.
After executing:
numbers = [[1, 2], [3, 4]]
Python creates three objects. Two inner lists: [1, 2] and [3, 4], and one outer list that refers to them. Visually:
numbers
β
βΌ
ββββββββββββββββ
β β’ β’ β
ββββΌβββββββΌβββββ
β β
βΌ βΌ
[1, 2] [3, 4]
Now we execute:
copy = numbers.copy()
Python creates a new outer list. However, the elements of that list still refer to the very same inner lists. Now the picture looks like this:
numbers ββ
β
βΌ
ββββββββββββββββ
β β’ β’ β
ββββΌβββββββΌβββββ
β β
βΌ βΌ
[1,2] [3,4]
β² β²
β β
ββββΌβββββββΌβββββ
β β’ β’ β
ββββββββββββββββ
β²
β
copy
Notice what changed. There are now two outer lists. But there are still only two inner lists. Thatβs why this line:
copy[0].append(99)
doesnβt really modify βthe copy.β It modifies one of the shared inner lists. Since both outer lists refer to that same object, the change becomes visible through both of them.
Why Use a Shallow Copy?
At this point, you might wonder: If copy() doesnβt make a completely independent copy, why does it exist at all? The answer depends on what your program actually needs. Very often, creating a new container is enough. For example:
numbers = [1, 2, 3]
copy = numbers.copy()
Here, the elements are numbers. And numbers are immutable. So a shallow copy is usually all you need.
The interesting cases begin when a collection contains other mutable objects. For example:
- lists
- dictionaries
- sets
In these situations, a shallow copy is no longer sufficient.
Deep Copy with deepcopy
Fortunately, Python also provides a way to create a completely independent copy. The copy module includes a function called deepcopy().
from copy import deepcopy
numbers = [[1, 2], [3, 4]]
copy = deepcopy(numbers)
copy[0].append(99)
print(numbers)
Now the output is: [[1, 2], [3, 4]]
Why? Because deepcopy() doesnβt stop at the outer list. It recursively creates copies of every nested object. After a deep copy, the two collections no longer share any of their inner lists.
Summary
We can now summarize the difference:
- A shallow copy creates a new container, but reuses the objects stored inside it.
- A deep copy recursively creates new objects throughout the entire object hierarchy.
Once again, nothing magical is happening. There are objects. There are references between those objects. copy() copies the references. deepcopy() creates new objects instead.
Whatβs Next?
By now, weβve built a pretty solid understanding of how Python objects are created, modified, and copied. But thereβs still one important question left. Sometimes it seems that an object is no longer needed. For example:
numbers = [1, 2, 3]
del numbers
What happens to the object itself? Does it disappear immediately? Or does Python keep it around for a while? Thatβs exactly what weβll explore in the next article.
Comments
No comments yet. Start the discussion.