Mutable vs. Immutable Objects in Python

Rate this post
Mutable vs. Immutable Objects in Python

Overview:

  • Mutable objects are Python objects that can be changed.
  • Immutable objects are Python objects that cannot be changed.
  • The difference originates from the fact the reflection of how various types of objects are actually represented in computer memory.
  • Be aware of these differences to avoid surprising bugs in your programs.

Introduction

To be proficient a Python programmer must master a number of skills.  Among those is an understanding of the notion of mutable vs immutable objects.  This is an important subject, as without attention to it programmers can create unexpected and subtle bugs in their programs.

As described above, at its most basic mutable objects can be changed, and immutable objects cannot be changed.  This is a simple description, but for a proper understanding, we need a little context.  Let’s explore this in the context of the Python data types.

Mutable vs. Immutable Data Types

The first place a programmer is likely to encounter mutable vs. immutable objects is with the Python data types. 

Here are the most common data types programmers initially encounter, and whether they are mutable or immutable (this is not a complete list; Python does have a few other data types):

Data typeMutable or Immutable?
intimmutable
floatimmutable
strimmutable
listmutable
tupleimmutable
dictmutable
boolimmutable

Let’s experiment with a few of these in the Python shell and observe their mutability/immutability.  

First let’s experiment with the list, which should be mutable.  We’ll start by creating a list:

>>> our_list1 = ['spam', 'eggs']

Now let’s try changing the list using a slicing assignment:

>>> our_list1[0] = 'toast'

Now let’s view our list and see if it has changed:

>>> our_list1
['toast', 'eggs']

Indeed, it has.

Now let’s experiment with integers, which should be immutable.  We’ll start by assigning an integer to our variable:

>>> our_int1 = 3
>>> our_int1
3

Now let’s try changing it:

>>> our_int1 = 42
>>> our_int1
42

It changed. If you’ve already worked with Python this should not surprise you. 

So in what sense is an integer immutable?  What’s going on here?  What do the Python language designers mean they claim integers are immutable?

It turns out the two cases are actually different.

  • In the case of the list, the variable still contains the original list but the list was modified. 
  • In the case of the integer, the original integer was completely removed and replaced with a new integer.

While this may seem intuitive in this example, it’s not always quite so clear as we’ll see later. 

Many of us start out understanding variables as containers for data. The reality, where data is stored in memory, is a little more complicated. 

The Python id() function will help us understand that.  

Looking Under the Hood: the id() Function

The common understanding of variables as containers for data is not quite right.  In reality variables contain references to where the data stored, rather than the actual data itself.

Every object or data in Python has an identifier integer value, and the id() function will show us that identifier (id).

In fact, that id is the (virtualized) memory location where that data is stored. 

Let’s try our previous examples and use the id() function to see what is happening in memory

🛑 Note: be aware that if you try this yourself your memory locations will be different.

>>> our_list1 = ['spam', 'eggs']
>>> id(our_list1)
139946630082696

So there’s a list at memory location 139946630082696

Now let’s change the list using a slicing assignment:

>>> our_list1[0] = 'toast'
>>> our_list1
['toast', 'eggs']
>>> id(our_list1)
139946630082696

The memory location referenced by our_list1 is still 139946630082696.  The same list is still there, it’s just been modified.

Now let’s repeat our integer experiment, again using the id() function to see what is happening in memory:

>>> our_int1 = 3
>>> our_int1
3
>>> id(our_int1)
9079072

So integer 3 is stored at memory location 9079072.  Now let’s try to change it:

>>> our_int1 = 42
>>> our_int1
42
>>> id(our_int1)
9080320

So our_int1 has not removed the integer 3 from memory location 9079072 and replaced it with integer 42 at location 9079072

Instead it is referencing an entirely new memory location. 

Memory location 9079072 was not changed, it was entirely replaced with memory location 9080320.  The original object, the integer 3, still remains at location 9079072

Depending on the specific type of object, if it is no longer used it will eventually be removed from memory entirely by Python’s garbage collection process. We won’t go into that level of detail in this article – thankfully Python takes care of this for us and we don’t need to worry about it. 

We’ve learned lists can be modified.  So here’s a little puzzle for you.  Let’s try modifying our list variable in a different way:

>>> our_list1 = ['spam', 'eggs']
>>> id(our_list1)
139946630082696
>>> our_list1  = ['toast', 'eggs']
>>> our_list1
['toast', 'eggs']
>>> id(our_list1)

What do you think the id will be?  Let’s see the answer:

>>> id(our_list1)
139946629319240

Woah, a new id!

Python has not modified the original list, it has replaced it with a brand new one. 

So lists can be modified, if something like assigning elements is done, but if instead a list is assigned to the variable, the old list is replaced with a new one. 

Remember: What happens to a list, whether being modified or replaced, depends on what you do with it.

However if ever you are unsure what is happening, you can always use the id() function to figure it out.

Mutable vs. Immutable Objects

So we’ve explored mutability in Python for data types. 

However, this notion applies to more than just data types – it applies to all objects in Python. 

And as you may have heard, EVERYTHING in Python is an object!

The topic of objects, classes, and object-oriented programming is vast, and beyond the scope of this article. You can start with an introduction to Python object-orientation in this blog tutorial:

Some objects are mutable, and some are immutable.  One notable case is programmer-created classes and objects — these are in general mutable.

Modifying a “Copy” of a Mutable Object

What happens if we want to copy one variable to another so that we can modify the copy:

normal_wear = ['hat', 'coat']
rain_wear = normal_wear

Our rainy weather wear is the same as our normal wear, but we want to modify our rainy wear to add an umbrella.  Before we do, let’s use id() to examine this more closely:

>>> id(normal_wear)
139946629319112
>>> id(rain_wear)
139946629319112

So the copy appears to actually be the same object as the original.  Let’s try modifying the copy:

>>> rain_wear.append('umbrella')
>>> rain_wear
['hat', 'coat', 'umbrella']
>>> normal_wear
['hat', 'coat', 'umbrella']

So what we learned from id() is true, our “copy” is actually the same object as the original, and modifying the “copy” modifies the original.  So watch out for this!

Python does provide a solution for this through the copy module.  We won’t examine that here, but just be aware of this issue, and know that a solution is available.

💡 Note: immutable objects behave almost the same. When an immutable value is copied to a second variable, both actually refer to the same object. The difference for the immutable case is that when the second variable is modified it gets a brand new object instead of modifying the original.

Bug Risk, and Power: Mutable Objects in Functions

If you’re not careful, the problem we saw in the last section, modifying a “copy” of a variable, can happen when writing a function. 

Suppose we had written a function to perform the change from the last section. 

Let’s write a short program dressForRain.py which includes such a function:

def prepForRain(outdoor_wear):
    outdoor_wear.append('umbrella')
    rain_outdoor_wear = outdoor_wear
    return rain_outdoor_wear

normal_wear = ['hat', 'coat']
print('Here is our normal wear:', normal_wear)
rain_wear = prepForRain(normal_wear)
print('Here is our rain wear:', rain_wear)
print('What happened to our normal wear?:', normal_wear)

We know that the data is passed into the function, and the new processed value is returned to the main program. 

We also know that the variable created within the function, the parameter outdoor_wear, is destroyed when the function is finished. 

Ideally this isolates the internal operation of the function from the main program.  

Let’s see the actual results from the program (A Linux implementation is shown.  A Windows implementation will be the same, but with a different prompt):

$ python dressForRain.py
Here is our normal wear: ['hat', 'coat']
Here is our rain wear: ['hat', 'coat', 'umbrella']
What happened to our normal wear?: ['hat', 'coat', 'umbrella']

Since variables normal_wear and outdoor_wear both point to the same mutable object, normal_wear is modified when outdoor_wear is appended, which you might not have intended, resulting in a potential bug in your program. 

Had these variables been pointing to an immutable object such as a tuple this would not have happened. Note, however, tuples do not support append, and a concatenation operation would have to be done instead.

Though we have shown some risk using lists in a function, there is also power as well. 

Functions can be used to modify lists directly, and since the original list is modified directly, no return statement would be needed to return a value back to the main program.

Tuple Mutable(?) ‘Gotcha’

Here is one last, perhaps surprising, behavior to note.  We’ve mentioned that tuples are immutable. 

Let’s explore this a little further with the following tuple:

>>> some_tuple = ('yadda', [1, 2])

Let’s try modifying this by adding 3 to the list it contains:

>>> some_tuple[1].append(3)

What do you think happens?  Let’s see:

>>> some_tuple
('yadda', [1, 2, 3])

Did our tuple change?  No it did not. It still contains the same list – it is the list within the tuple that has changed. 

You can try the id() function on the list portion of the tuple to confirm it’s the same list.

Why Bother with Mutable vs. Immutable?

This mutable/immutable situation may seem a bit complicated. 

Why did the Python designers do this? Wouldn’t it have been simpler to make all objects mutable, or all objects immutable?

Both mutable and immutable properties have advantages and disadvantages, so it comes down to design preferences.

Advantage: For instance, one major performance advantage of using immutable instead of mutable data types is that a potentially large number of variables can refer to a single immutable object without risking problems arising from overshadowing or aliasing. If the object would be mutable, each variable would have to refer to a copy of the same object which would incur much higher memory overhead.

These choices are affected by how objects are typically used, and these choices affect language and program performance. Language designers take these factors into account when making those choices. 

Be aware that other languages address the mutable/immutable topic as well, but they do not all implement these properties in the same way. 

We will not go into more detail on this in this article.  Your appreciation of these choices will develop in the future as you gain more experience with programming.

Conclusion

  • We have noted that Python makes some of its objects mutable and some immutable. 
  • We have explored what this means, and what some of the practical consequences of this are. 
  • We have noted how this is a consequence of how objects are stored in memory, and
  • We have introduced Python’s id() function as a way to better follow this memory use.

High-level programming languages are an ever-advancing effort to make programming easier, freeing programmers to produce great software without having to grapple with the minute details as the computer sees it. 

Being aware of how mutable and immutable objects are handled in memory is one case where a bit more awareness of the details of the computer will reap rewards.  Keep these details in mind and ensure your programs perform at their best.