Mutable vs Immutable Objects

Object-oriented programming (OOP) is a programming language model organized around objects rather than “actions” and data rather than logic.

Think of an object in python as a block of memory. And a variable is just something that points/references to that block of memory. All the information relevant to your data is stored within the object itself. And the variable stores the address to that object. So it actually doesn’t matter if you reassign a variable pointing to an integer to point to a different data type.

>>> a = 1
>>> a = "I am a string now"
>>> print(a)
I am a string now

Every object has its own identity/ID that stores its address in memory. Every object has a type. An object can also hold references to other objects. For example, an integer will not have references to other objects but if the object is a list, it will contain references to each object within this list. We will touch up on this when we look at tuples later.

The builtin function id() will return an object’s id and type() will return an object’s type:

>>> list_1 = [1, 2, 3]
# to access this object's value
>>> list_1
[1, 2, 3]
# to access this object's ID
>>> id(list_1)
# to access object's data type
>>> type(list_1)
<class 'list'>

Immutable objects:

Objects in python can be either mutable or immutable. Immutable objects cannot be changed after initialization / assignment. Strings are an example of an immutable object. Once the string is created, it it set. So if we try to change the first character of the string "abcd" from 'a' to 'z', an error will occur:

""" Creating and attempting to change first character """
>>> string_1 = "abcd"
>>> string_1[0]
>>> string_1[0] = 'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

To change the content of a string, you can reassign the variable to point to a new object instead:

""" Changing content of string1 """
>>> string1 = "Apples"
>>> id(string1)
4321890520 # id of current string
>>> string1 += " and oranges"
>>> string1
'Apples and oranges'
>>> id(string1)
4321873688 # string1 now points to a different string

Common immutable data types include int, float, long, str, bytes, tuple, frozen set, etc.

Tuples are handled a bit differently.

Although they are immutable, their content has the potential to contain mutable objects. We talked about how some objects contain references earlier. While this tuple physically points to its objects, it doesn’t change the fact that lists are mutable:

>>> tuple_a = ('waffles', 1, ['vanilla', 'chocolate'])
# cannot change value 'waffles'
>>> tuple_a[0] = 'banana'
Traceback (most recent call last):
TypeError: 'tuple' object does not support item assignment
# can't append
>>> tuple_a.append('banana')
Traceback (most recent call last):
AttributeError: 'tuple' object has no attribute 'append'
#attempting to append to list within tuple
>>> tuple_a[2].append('strawberry')
# an object within a tuple changed!
>>> tuple_a
('waffles', 1, ['vanilla', 'chocolate', 'strawberry'])
Here we have a list within a tuple that points to two other objects, ‘vanilla’ and ‘chocolate’
Appending ‘strawberry’ to the list will not change the object to which tuple_a[2] points

Mutable objects:

Mutable objects can be altered directly. You can change their content without changing their identity. Common mutable types include byte array, list, dict, set, user-defined classes (if not specified as immutable).

Notice below how the variable list_1 points to the same memory address before and after appending ‘chocolate cake’ to the list:

>>> list_1 = ['pizza', 'fries', 'donuts']
>>> id(list_1)
>>> list_1.append('chocolate cake')
>>> list_1
['pizza', 'fries', 'donuts', 'chocolate cake']
>>> id(list_1)

It is important to understand the differences between mutable and immutable objects as they are treated differently in python.

Here are two sets of code:

""" Strings """
>>> a = "apple"
>>> b = a
>>> a = "orange"
>>> b
>>> # b still points to the same string after a changes to "orange"
""" Lists """
>>> list1 = ['coconut cream pie', 'pumpkin pie', 'apple pie']
>>> list2 = list1
>>> list1[0] = 'lemon meringue pie'
>>> list2
['lemon meringue pie', 'pumpkin pie', 'apple pie']
>>> # contents of both lists changed when list1 was changed

Why isb unchanged but list2 changed? Think about mutability again. When we first assigned b = a, both a and b are pointing to the same object — 'apple'. When we changed the value of a, we didn’t alter the string 'apple'. a was actually reassigned to a different object — 'orange'. So now a and b are no longer pointing at the same object. See below:

Since lists are mutable, we can directly make changes to them. list2 changed when list1 changed because they are still pointing at the same object. Since list1and list2 are aliases of the same list, changes in one alias will affect the other, as they are both point to the exact same object.

Passing mutable and immutable objects into functions:

For immutable objects, if the value of an object is changed inside the function, a local duplicate will be created so the caller function’s object will remain unchanged:

>>> def function1(x):
... x += 1
... print("x = ",x)
... print("id of x in function1 = ", id(x))
>>> x = 5
>>> id(x)
>>> function1(x)
x = 6 # value of x inside function1
id of x in function1 = 10055712 # x is a different object here
# value of x remains unchanged outside of function1
>>> x

When a mutable object is passed into a function, the function can change that object’s content directly. For example, we can create a function that appends 9 to a list and pass the list b into it. This will directly change the content of this list with an additional 9 value:

>>> b = [6, 7, 8]
>>> def adding(x):
... x.append(9)
>>> adding(b)
>>> b
[6, 7, 8, 9]

Why bother?

Utilizing the nature of mutable and immutable objects can greatly increase the efficiency of your code(and lower chances for some bugs). The simplest example is string concatenation. Remember the values of immutable objects cannot be changed. They will alocate new memory and point to a new object if you want the variable to hold a different value. This can become costly if you have to repeat over and over. Below are 2 differnt ways to concatenate strings. One is more efficient than the other:

Both will yield the same result, "MydogRuby", but the first block of code creates a new string for every iteration of the for loop. Whereas in the second code block, we utilize the string method .join() — which concatenates all the elements of an iterable and then converts to a string. The latter is much more efficient.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.