Mutable vs Immutable Objects in Python
Everything is an object in Python, but what does that mean?
Python is an amazing language. Due to its simplicity, many people choose it as their first programming language. Experienced programmers also use Python all the time, thanks to its wide community, abundance of packages, and clear syntax. But there is one problem that seems to confuse both beginners and some seasoned developers: Python objects. Specifically, the difference between mutable and immutable objects. In this post we will review the knowledge about Python objects, learn the difference between mutable and immutable objects, and see how we can use the interpreter to better understand how Python works.
What is an object?
An object is anything that a variable can point to. In Python, everything (including a class) is an object. (In fact, classes as objects have their own class: a metaclass.) A particularly common object is an int object, or integer. Other objects include strings, dictionaries, and lists.
Objects contain data but behave like functions; they act in the way expected of them based on their type. Objects have attributes which are associated with specific objects and take specific values. Objects also have methods which act as functions using both the object and its arguments.
In Object-oriented programming (OOP), computer programs are designed by making them out of objects that interact with one another. Now to have an object you first need a class. Classes are the definitions for the data format and available procedures for a given type or class of object and objects are instances of classes.
For example, if we want to make an object called Person. We first create and initialize a class and then add attributes and methods.
""" defines a new person """
def __init__(name="", age=0)
""" initializes person """
self.name = name
self.age = age def print_name(self):
""" prints name of the person """
If we later want to initialize a new person, we can use the class and enter our attributes as arguments.
>>> jorge = Person("Jorge", 28)
Identification and type functions
What are id() and type() in Python? id () and type () are two built-in functions used to get information about various objects.
The id() function returns the id of an object; The Python standard library defines id as an integer (or long integer) that is guaranteed to be unique and constant for this object throughout its lifetime. Although it is not a memory address, it works similarly to how an address would in C and its related languages. For example:
# Declaring two string objects:
>>> obj1 = "Hi"
>>> obj2 = "Galia"# Getting their ids:
As you can see,
obj2 each have their own unique ids. Ok, now what happens if I assign two objects to point to the same thing like the below:
>>> obj3 = "Galia"
>>> obj4 = "Galia"
What would happen if I ask for the id of both? Would they be the same or different?
139725014575280>>>id(obj3) == id(obj4)
True>>>id(obj3) is id(obj4)
They are the same, but why? When two objects are referring to the same value, this is known as an alias. The id of an object is used to help differentiate between two variables being identical and them being linked to the same object. We can use “==” to determine if they are identical, while “is” can be used to determine if both variables are pointing to the same object. However, what happens if the objects are mutable? We’ll talk about that in the next section.
Cool fact: Python stores numbers from -5 to 256 as a built-in. So, whenever you assign 2 variables to the same number, the variable is actually a pointer pointing to the memory location where the integer is stored.
The type() function is used to “return the type of an object” and its “return value is a type object and generally the same object as returned by
object.__class__. So, if you wanted to find out what type is your variable, you can do this:
>>> a = 50
<class: 'int'>>>> b = "Holberton"
<class: 'string'>>>> type(a) == type(b)
Now we have seen how to compare two simple string variables to find out the types and identifiers. So using these two functions we can check how different types of objects are associated with variables and how objects can be changed.
Mutable and Immutable Objects
As mentioned earlier, when an object is created it is assigned a unique object ID. Depending on the type of object, a mutable object can be changed after it is created and an immutable object cannot be changed.
Objects of built-in types like (int, float, str, tuple) are immutable while objects of built-in types like (list, set, dict) are mutable. Custom classes are generally mutable, but can be configured to simulate immutability.
Mutable objects are those that can be changed after creation. The most common mutable objects in Python are lists. Mutable objects are very easy to change and are often used in cases where you might want to modify a value later on. Let’s make a list:
>>> my_list = [1, 2, 3, 4]
Now let’s say we want to add another element to our list.
[1, 2, 3, 4, 5]
We can see that the ID has not changed. This is the same list, it just has different contents.
Immutable objects consists of
frozen set, and
bytes. These are objects that can’t be manipulated; for example:
# Declaring a tuple object:
>>> a = (1, 2, 3)
>>> a.append(3)# Getting an error when I try to append a new number to the tupleTraceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'
I can’t add or change the original tuple.
Why does it matter and how differently does Python treat mutable and immutable objects
From examples above we can see that integers and strings are stored in one location and the variables are pointing to that same location. Since we are looking at it form object oriented perspective we know everything is an object in python so no matter how many variables we have pointing to 6 or “Hello” there is only one instance of it in the memory and all the variables are just pointing to that same object. So they are all aliases in a way. Ok, so that makes sense but what about floats and tuples. They are immutable, so why don’t they have the same addresses. Let’s keep checking and see if we can find an answer.
>>> b += 1 >>> d = (1, 2, 3, 4)
>>> id(b) >>> id(d)
>>> b -= 1 >>> id((1, 2, 3, 4))
>>> id(b) 139750517631640
140634296045688 >>> a = (1, )
>>> b -= 1 >>> type(a)
>>> id(b) <class 'tuple'>
140634296045976 >>> a = (1, 2)
>>> b += 5 >>> b = (1, 2)
>>> id(b) >>> a is b
>>> b >>> a = ()
10.7 >>> b = ()
>>> id(10.7) >>> a is b
Seems like the floats are being stored in two addresses only in this case. I’m sure that’s not the case if we are working with a lot more numbers stored in different variables where each variable would get it’s own address to store it because we know floats are immutable too and would need a different memory location for each number.
This time around I used ‘is’ to check if they are the same object. So the difference between using ‘==’ and ‘is’ is that that ‘==’ compares the values of the objects where ‘is’ actually tells you if it’s the same object two variables are pointing to.
One of the most important things to know is how to save memory when using strings. This is where understanding of how mutable vs. immutable object are treated in Python can come in really handy. We know strings are immutable so if we want to put two strings together we would have to create a whole new object and use extra memory.
How arguments are passed to functions and what does that imply (for mutable and immutable objects)
Now one more thing we need to take into consideration is how are mutable vs. immutable objects are being affected when they are passed to functions.
Let’s take a look at few examples:
n += 1>>> a = 1
What happened here? Well… ‘n’ is just a variable so it’s ’n’ that’s being changed inside the increment function not ‘a’. Integers are immutable so we can’t changed them even inside the function. It’s only what variables are pointing to that‘s being changed.
n.append(4)>>> l = [1, 2, 3]
[1, 2, 3, 4]
But with lists that’s a different story. Since lists are mutable we can pass them to functions and change them inside of the functions too. But to do that we need to know how to do it and we do it by using appropriate functions to do that. We can’t just use new variables that don’t exist outside of the scope of the function and expect mutable objects to change. Like in this case for example:
def assign_value(n, v):
n = v>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> assign_value(l1, l2)
[1, 2, 3]
With mutable objects we also have to pay attention in case we want to keep two different versions of a list for example. If we want to have old list and changed list available we would have to so call “clone” the original and perform any operations on the copy. We clone the list like this:
>>> a = [1, 2, 3]
>>> b = a[:]
So that’s my quick and easy introduction to Python objects, also the examples presented helped me understand Python a little better and understand the difference between mutable and immutable objects and how to use them efficiently. For more information, visit the official Python tutorial.