Introduction:
Python is an interpreted high-level general-purpose programming language in it everything is considered as an object.
Objects in Python can be either mutable or immutable.
In this blog we are going to explain this notions:
- id and type
- mutable objects
- immutable objects
- why does it matter and how differently does Python treat mutable and immutable objects
- how arguments are passed to functions and what does that imply for mutable and immutable objects
id and Type
- id
According to the official Python documentation, the id() function returns identity (unique integer of an object). This function determine if we’re creating new objects or reusing existing ones.
For example:
>>> a = 98
>>> b = 98
>>> id(a)
9788000
>>> id(b)
9788000
>>>
a and b both have the same identity, so they are pointing to the same instance of 98
=> If a != b the id value will not remain the same.
>>> a = 98
>>> b = 99
>>> id(a)
9788000
>>> id(b)
9788032
>>>
- type:
The type() built-in returns what the type of an object is. It should be noted that since everything is an object even types have a type, which is type.
>>> a = 98
>>> b = (1,1)
>>> c = (1, )
>>> d = ()
>>> type(a)
<class 'int'>
>>> type(b)
<class 'tuple'>
>>> type(c)
<class 'tuple'>
>>> type(d)
<class 'tuple'>
>>>
- NB:
>>> e = (1)
>>> type(e)
<class 'int'>
>>>
In this example a single integer between two parentheses will just be stored as an integer.
Mutable and Immutable Objects
Since everything in Python is an Object, every variable holds an object instance.
When an object is initiated, it is assigned a unique object id. Its type is defined at runtime and once set can never change, however its state can be changed if it is mutable. Simple put, a mutable object can be changed after it is created, and an immutable object can’t.
As we see in this table, objects of built-in types like (int, float, bool, str, tuple, …) are immutable, otherwise, objects of built-in types like (list, set, dict) are mutable.
Let’s see an example of immutable objects:
>>> x = 10
>>> y = x
>>> y
10
We are creating an object of type int for x and y points to the same object.
>>> x = 4
>>> y = x
>>> id(x) == id(y)
True
>>> id(y) == id(4)
True
>>>
- Let’s do some changes
x = x + 1
Now =>
>>> x = 4
>>> y = x
>>> x += 1
>>> id(x) == id(y)
False
>>>
The object in which x pointed is changed.
x = 4; this object of type int was never modified.
Immutable objects doesn’t allow modification after creation !
- In the case of mutable objects:
>>> m = list([0, 1, 2])
>>> n = m
>>> id(m) == id(n)
True
>>> m.pop()
2
>>> id(m) == id(n)
True
>>>
We created an object of type list were m and n point to the same list object, which is a collection of 3 immutable int objects.
After modifying the object by deleting an item from the list object we see that
object id will not be changed and m and n will be pointing to the same list object after the modification.
- Python handles mutable and immutable objects differently.
- Immutable are quicker to access than mutable objects.
- Mutable objects are great to use when you need to change the size of the object, example list, dict etc..
- Immutable objects are used when you need to ensure that the object you made will always stay the same.
- Immutable objects are fundamentally expensive to “change”, because doing so involves creating a copy. Changing mutable objects is cheap.
Why does it matter? How does Python treat mutable and immutable objects?
Let’s try with this example:
>>> i = "Holberton"
>>> id(i)
140700428457456
>>> i+= "School"
>>> i
'HolbertonSchool'
>>> id(i)
140700428425712
>>>
As we can see the concatenation of i and the string created a new object i with type string that contains the value of both the old object i = “Holberton” and the string “School”.
This means that changing an immutable type result a creation of a new copy. This can be a very expensive process for extremely long strings or dictionaries. On the flip side, mutable objects are easier to mutate.
Now let’s see what happens when you add a mutable object to an immutable object like a tuple?
>>> i = ("Holberton", [2, 5, 7])
>>> id(i)
140700428447040
>>> type(i)
<class 'tuple'>
>>> id(i[0])
140700428457456
>>> type(i[0])
<class 'str'>
>>> id(i[1])
140700439792128
>>> type(i[1])
<class 'list'>
>>>
Here, I have created an object i with the class tuple which holds a reference to a string, which is immutable, and a list, which is mutable.
>>> i[1].append(4)
>>> i
('Holberton', [2, 5, 7, 4])
>>> id(i[1])
140700439792128
>>>
Since the list is mutable, I can use list built ins to modify the list itself.
As we can see, none of the ids have changed meaning the mutable object list within the immutable tuple is the same as before. So what is immutable is the content of the tuple itself meaning the references to the string and the list.
So this means that immutable objects within an immutable object cannot be changed as the only way to change an immutable object is to make a new object with the updated values.
How arguments are passed to functions and what does that imply for mutable and immutable objects?
Arguments are always passed to functions by reference in Python. The caller and the function code blocks share the same object or variable. When we change the value of a function argument inside the function code block scope, the value of that variable also changes inside the caller code block scope regardless of the name of the argument or variable. This concept behaves differently for both mutable and immutable arguments in Python.
>>> def update(input):
... print(id(input))
... input = input + [10]
... print(id(input))
...
>>> i = [5, 7]
>>> id(i)
140700439792128
>>> update(i)
140700439792128
140700430041728
>>> i
[5, 7]
>>> id(i)
140700439792128
>>>
This a simple program that takes in an input and adds a list [10] to the input. I have passed in i which is the list [5, 7] and got back an updated list with the 10 in it. As you have noticed, all of the addresses are the same whether inside or outside the function. Since the object i was a mutable object, Python has resolved the parameter passing of i similar to call by reference.
- For immutable object:
>>> def update(input):
... print(id(input))
... input = input + "Rym.holbie"
... print(input)
... print(id(input))
>>> i = "Hi! "
>>> i
'Hi! '
>>> id(i)140700428425712
>>> update(i)140700428425712
Hi! Rym.holbie
140700449828784
>>> id(i)
140700428425712
>>> i
'Hi! '
>>>
In this example, the program will add a string”Rym.holbie” to the input, which will also be a string.
Based on the ids printed, we can see that when an immutable value is passed into the function, Python behaves like a call by reference.
But since we are “changing” an immutable object, Python’s behavior “switches” to call by value and creates a new string object to hold the newly created object. Of course, the original input was never changed as the new object created within the program was never saved anywhere and was discarded.
Conclusion
- Mutable and immutable objects are handled differently in python. Immutable objects are quicker to access and are expensive to change because it involves the creation of a copy.
However, mutable objects are easy to change. - Use of mutable objects is recommended when there is a need to change the size or content of the object.
- However, there is an exception in immutability as well. We know that tuple in python is immutable. But the tuple consists of a sequence of names with unchangeable bindings to objects.