Mutability
int
, float
, str
and tuple
are examples for immutable data types. On the other hand, types like list
and dict
are mutable. This chapter will discuss what happens when you pass a variable to a function or when you assign them to another value/variable.
id
The id() built-in function returns the identity (reference) of an object. Here are some examples to show what happens when you assign a variable to another value/variable.
>>> num1 = 5
>>> id(num1)
140204812958128
# here, num1 gets a new identity
>>> num1 = 10
>>> id(num1)
140204812958288
# num2 will have the same reference as num1
>>> num2 = num1
>>> id(num2)
140204812958288
# num2 gets a new reference, num1 won't be affected
>>> num2 = 4
>>> id(num2)
140204812958096
>>> num1
10
Pass by reference
Variables in Python store references to an object, not their values. When you pass a list
object to a function, you are passing the reference to this object. Since list
is mutable, any in-place changes made to this object within the function will also be reflected in the original variable that was passed to the function. Here's an example:
>>> def rotate(ip):
... ip.insert(0, ip.pop())
...
>>> nums = [321, 1, 1, 0, 5.3, 2]
>>> rotate(nums)
>>> nums
[2, 321, 1, 1, 0, 5.3]
This is true even for slices of a sequence containing mutable objects. Also, as shown in the example below, tuple
doesn't prevent mutable elements from being changed.
>>> nums_2d = ([1, 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200])
>>> last_two = nums_2d[-2:]
>>> last_two[0][-1] = 'apple'
>>> last_two[1][-1] = 'ball'
>>> last_two
([1.2, -0.2, 0, 'apple'], [100, 'ball'])
>>> nums_2d
([1, 3, 2, 10], [1.2, -0.2, 0, 'apple'], [100, 'ball'])
As an exercise, use the id()
function to verify that the identity of the last two elements of the nums_2d
variable in the above example is the same as the identity of both the elements in the last_two
variable.
Slicing notation shallow copy
If you wish to copy whole or part of a list
object such that changing the copy version doesn't affect the original list
, the solution will depend on the presence of mutable elements.
Here's an example where all the elements are immutable. In this case, using slice notation is safe for copying.
>>> items = [3, 'apple', 100.23, 'fig']
>>> items_copy = items[:]
>>> id(items)
140204765864256
>>> id(items_copy)
140204765771968
# the individual elements will still have the same reference
>>> id(items[0]) == id(items_copy[0])
True
>>> items_copy[0] += 1000
>>> items_copy
[1003, 'apple', 100.23, 'fig']
>>> items
[3, 'apple', 100.23, 'fig']
On the other hand, if the sequence has mutable objects, a shallow copy made using slicing notation won't stop the copy from modifying the original.
>>> nums_2d = [[1, 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200]]
>>> nums_2d_copy = nums_2d[:]
>>> nums_2d_copy[0][0] = 'oops'
>>> nums_2d_copy
[['oops', 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200]]
>>> nums_2d
[['oops', 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200]]
copy.deepcopy
The copy built-in module has a deepcopy()
method if you wish to recursively create new copies of all the elements of a mutable object.
>>> import copy
>>> nums_2d = [[1, 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200]]
>>> nums_2d_deepcopy = copy.deepcopy(nums_2d)
>>> nums_2d_deepcopy[0][0] = 'yay'
>>> nums_2d_deepcopy
[['yay', 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200]]
>>> nums_2d
[[1, 3, 2, 10], [1.2, -0.2, 0, 2], [100, 200]]
As an exercise, create a deepcopy of only the first two elements of the nums_2d
object from the above example.