Today we’ll be talking about the notion of shared reference. What are shared references in Python?
Multiple References to Immutable Types
As you know, the names to which we assign objects, reference the objects. We can assign an object to multiple names, so one object may be referenced by multiple names. We can use the id function to check whether two variables reference two distinct objects or just one object:
>>> a = 1000
>>> b = a
>>> c = b
>>> id(a) == id(b) == id(c)
True
Alternatively, we can use the is operator:
>>> a is b is c
True
Anyway, as you can see, the three variables, a, b and c, are all referencing the same object.
Multiple References to Mutable Types – the Shared Reference
If the object is an immutable type, like the integer or string in the examples above, you’re quite safe because they can’t be changed. But what if a mutable type is referenced more than once? Let’s use a list to illustrate this:
>>> nums = [1, 2, 3]
Let’s create a list containing just one element, our nums list, and then use the repetition operator to repeat the list containing our nums list 3 times:
>>> numlists = [nums] * 3
>>> numlists
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
Now let’s change an element in the nums list:
>>> nums[1] = 5
>>> nums
[1, 5, 3]
See what happens if we print our numlists list:
>>> numlists
[[1, 5, 3], [1, 5, 3], [1, 5, 3]]
Turns out the change propagated to numlists. This is because of the shared references to the original object in memory. The nums object and the three elements of numlists all reference the same list in memory, so if we change the list in any part of the code, the change will be made in place and will be visible everywhere. Let’s change just one element inside the last element of numlists and see what happens:
>>> numlists[2][2] = 11
>>> nums
[1, 5, 11]
>>> numlists
[[1, 5, 11], [1, 5, 11], [1, 5, 11]]
Again, because of the shared references to one and the same object, the change is now visible everywhere.
But what if you don’t want this behavior? Well, you can use a copy of the list instead of the original. Anyway, let’s use a copy and see what happens:
>>> nums = [1, 2, 3]
>>> numlists = [nums[:]] * 3
>>> numlists
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
Let’s change an element in nums:
>>> nums[1] = 5
>>> nums
[1, 5, 3]
Let’s test whether this change also propagated to numlists:
>>> numlists
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
Well, it didn’t, because now the list referenced by nums and the list referenced by all the elements of numlists are two distinct objects.
But what if we change one element in the first element of numlists:
>>> numlists[0][2] = 11
>>> nums
[1, 5, 3]
>>> numlists
[[1, 2, 11], [1, 2, 11], [1, 2, 11]]
As expected, it had no effect on nums, which is now referencing a distinct object. But the change is now visible in all the elements inside numlists. This is because the three elements of numlists are referencing the same object, which was created as a copy of the original list. If you want each sublist in numlists to reference a distinct object, you must create a copy for each element of numlists. To do that, you can use a for loop, or even better, a list comprehension:
>>> nums = [1, 2, 3]
>>> numlists = [nums[:] for n in range(3)]
>>> numlists
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
Let’s change an element in the first sublist:
>>> numlists[0][2] = 11
>>> numlists
[[1, 2, 11], [1, 2, 3], [1, 2, 3]]
Now the change is visible only in the first sublist. This is because now the three elements of numlist are referencing three distinct objects.
Here you can watch the video version: