DEV Community

loading...
Cover image for Mutable effect in Python

Mutable effect in Python

Sundeep
Addicted to writing books and teaching. Likes fantasy books, comics and anime
Originally published at learnbyexample.github.io ・3 min read

int, float, str and tuple are 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's 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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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'])
Enter fullscreen mode Exit fullscreen mode

As an exercise, use id() function to verify that the identity of last two elements of nums_2d variable in the above example is the same as the identity of both the elements of last_two variable.

Slicing notation copy

If you wish to copy whole/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']
Enter fullscreen mode Exit fullscreen mode

On the other hand, if the list has mutable objects, using slice 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]]
Enter fullscreen mode Exit fullscreen mode

copy.deepcopy

The copy built-in module has a deepcopy() method if you wish to recursively create new copy 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]]
Enter fullscreen mode Exit fullscreen mode

As an exercise, create a deepcopy of only the first two elements of nums_2d object from the above example.

Discussion (0)