Copies and Shared Objects
Quiz: counts toward finishing the lesson. Check: just for practice.
This page continues from Object References and Identity. It covers what happens when you copy a list, the difference between a shallow and a deep copy, and the cases where Python makes variables share an object for you.
Shallow Copies vs. Deep Copies
We can make a copy of a list like this:
copied_list now holds a copy of original_list, but it's just a shallow copy. The outer list is a separate object:
But the elements are shared. The first element (the int) is the same object in both:
And so is the second element (the inner list):
We can see that although original_list and copied_list refer to different objects, the elements of original_list and the elements of copied_list refer to the same objects. If you mutate the list referenced by original_list[1], and then print copied_list:
You'll see that copied_list shows that change as well.
Diagram:
original_listandcopied_listare two separate outer list objects, so each has its own box. But their second element (the inner list[1, 2, 3]) is a single shared object: the second slot of both outer lists has an arrow pointing to that one inner list. Their first element (the int1) is also shared. Mutating the shared inner list is therefore visible through both outer lists.
A deep copy, however, would be an entirely separate copy (no shared sub-elements or sub-sub-elements, etc.). If we had made a deep copy in this example instead of a shallow copy, it would look like this:
Diagram:
original_listandcopied_listare separate outer list objects, and this time their inner lists are also separate objects. Every slot incopied_listpoints at its own copy, so there are no shared arrows between the two structures at any level. Mutating one structure cannot affect the other.
We could have achieved a deep copy by also making a copy of any sublists (or other mutable sub-elements). Here the inner lists end up as separate objects:
There is a Python module that can be used to make deep copies, but we won't cover that here.
Continuing with our shallow copy example, if you rebind original_list[0] and then print copied_list:
You'll see that copied_list hasn't changed. That's because we're not mutating the object referred to by original_list[0] (it's an int, and ints are immutable). Instead, we're rebinding original_list[0] to a new int object.
Diagram: starting from the shallow copy,
original_list[0]now has an arrow pointing at a new int object5, whilecopied_list[0]still points at the old int object1. The inner list in the second slot is still shared by both outer lists. Rebinding one slot oforiginal_listdoes not touchcopied_list.
We were able to mutate the object referred to by original_list[1] because it's a list, and lists are mutable. If we wanted, we could rebind original_list[1] instead, and copied_list wouldn't be affected, because we're not mutating a shared list, but instead rebinding original_list[1] to a new list object:
Check your understanding. Predict the result. One of the names is an alias and one is a copy.
a = [1, 2, 3]
b = a
c = list(a)
a.append(4)
Predict what each variable holds after the program finishes. The program is not run for you. Change the values below to your predictions:
final_ais the value ofaat the endfinal_bis the value ofbat the endfinal_cis the value ofcat the end
Hint 1
b is another name for the same list as a, so it sees the append. c is a copy made before the append, so it does not.
Automatic Sharing
Sometimes Python is able to figure out that it can save memory by automatically making variables refer to the same object.
This only ever happens with (equal) immutable values, so it can't affect your code's results.
A note about Python internals:
You might notice that this example using 5 gives a different result from the example that used 1000 on the previous page. It depends too on whether you're running the code in the interactive console or running it as a script. In the interactive console, Python internally keeps an array of integer objects for -5 through 256. When you create an int in that range, you actually just get back a reference to the existing object. So if you do that with two (or more) variables, they will then refer to the same object. However, if you type that code as a script in an IDE and then try running it with different values, you can see that any integer values will be shared, not just from -5 to 256. This is because when you're running a script, the Python interpreter can look through the entire script before running it, so it knows all the values that could be shared.
Quizzes
- Predict the result. The copy is a shallow copy.
original = [1, [2, 3]]
copied = list(original)
copied[0] = 99
copied[1].append(4)
Predict what each variable holds after the program finishes. The program is not run for you. Change the values below to your predictions:
final_originalis the value oforiginalat the endfinal_copiedis the value ofcopiedat the end
Hint 1
copied[0] = 99 replaces an item in the copy only. copied[1] is the same inner list as original[1], so appending to it shows up in both.