Python3: Mutable, Immutable... everything is object!
Everything is an object in python! that's right, python is an object oriented programming language, and as such, it treats all data types as objects, an integer is an object, a string is an object.?
As we know, an object is a unique instance of a data structure that is defined by its class, each object has attributes and is identified by: its type, which tells us the type or class to which an object belongs (integer, float, string.... ) and we can obtain it using the built-in function type(), its identity, which univocally identifies the object and we can obtain it using the built-in function id(), its value, every object has some particular characteristics, if these characteristics can be modified, we will say that it is a mutable type, otherwise we will say that it is immutable.?
So when we create a variable and assign it a value of any type, that value is an object, for example:
a = "Hello"
b = 12
In this case, "Hello" is an object of the class "string" and 12 is an object of the class "int". Let's see:
Functions, lists, dictionaries, tuples, all kinds of sets are also objects.
In this blog post we will talk about topics such as the identifier variable id, the function type and mutability and immutability of objects.
Id and Type
In Python, we have some built-in functions that help us to identify an object either by its memory address (id()) or by its type/class (type()).
Id()
On the one hand we have the built-in function id(), which returns a number that identifies an object at a particular runtime, this "identifier" guarantees that this id is unique for a given object. Consider the following example:?
As we can observe, the object numbers, has the same "identifier" every time we query it, since we are calling the same object at the same runtime, now let's see what happens if we query the id(identifier) of numbers from the show_numbers_id function:
The id number remains the same since we are querying the id of the same object at the same runtime. Note how in the following example the id of numbers has changed, this is because the runtime is different from the previous one.
type()
In python we do not have to say what type of variable is the one we are declaring since it detects it automatically, to know what type/class a variable belongs to, we use the built-in function type(), since it returns this information for a given object. Let's see how to use it:
However if we want to take subclasses into account to check the type/class of an object, we must use the built-in function isinstance().
Mutable Objects
A mutable object is one whose value or content can be modified and when doing so its memory address will not change during execution, this can be checked by consulting the object's id during runtime every time we make changes to it.
Among the mutable objects we find: lists, dictionaries, sets, user-defined classes, etc. These objects have their own methods that allow us to modify their value, for example, we can add elements to lists at the end using the append() method, to dictionaries we can add and remove keys and we are still operating on the same object even if we modify it.
Let's consider the following:
The first thing we did was to create an object of class "list" with the names list_1 and list_2, so both names are referencing the same object. Notice that list_1 and list_2 have the same id, so we are sure that they are the same object, then we do what is called in-place modification using the methods append() and extend(), it means that we take the same object and inside it we change the content. When printing list_1 and list_2 we can notice that the changes were reflected in both names since being the same object, what is done in one will also be seen in the other. The is operator is used to know if the memory address of the two variables is the same as in this case.?
Immutable Objects
An immutable object contains a value that is read-only, i.e. the value of the object cannot be modified. The types of immutable objects are: numbers, strings, booleans y tuples. When we create an immutable object, it will remain at the same memory address and if we assign another value to the variable, it will mean that we are creating another object to which we are assigning the name that the previous object had. To better understand this let's see the following example:?
In this example we have created object 12 which we have assigned to variable a, as we can see, the memory address returned by the built-in function id() for object 12 is 10914848, after this we have created object 14 and assigned it to variable a, we know that it is a different object because when we check the id of a again, it returns a different memory address than the one obtained by object 12, in this case object 14 is allocated at memory address 10914912. To verify that object 12 was not modified but a new object was created, we query again the id of objects 12 and 14 and obtain the same memory addresses that the id function returned previously for each object respectively within the same execution.
We will go more in depth later on how python treats these objects according to their type.
Why does it matter and how differently does Python treat mutable and immutable objects?
Python treats mutable and immutable objects differently, in the case of immutable objects, we can say that due to their immutability, when we have two variables that contain the same value, python saves resources by creating a single object since during execution this value cannot be changed.
Consider the following:
a = "Hello"
b = "Hello"
领英推荐
As we mentioned before, in this case python only creates one object with the value "Hello", it has been created only once and it has been assigned to two different names, which point to the same object, so if we query the id for a and for b, it will be the same:
Now let's see what happens here:
In this case we have the same object "Hello", which is pointed to by a, then we tell b to point to the same object that a is pointing to, then we have told a to point to a new object "World", but b still points to the object "Hello". This happens because the variable names are pointers, that is to say that a name in python points to an object, the equal sign (=) what it does is to copy the reference to which we are pointing so when we use the equal sign (=) to modify the value, we are not changing the value of the object, we are assigning to a different one. So, in the case of mutable objects, when we copy the reference to a mutable object in two different names and then we modify the content, we are changing the content in the two references, because in reality there is only one object.
In the case of the example of the lists mentioned in the mutable objects section, when we say list_1 = list_2 = [], we are actually saying that the equal sign (=) is assigning to list_1 the reference to which list_2 points, which in turn is a mutable object, this is called aliasing.
In mutable objects, unlike immutable objects, even if we have the same value in two different names, python will create two objects in different memory spaces, this is precisely because a mutable object can change during execution, so if we want the object to be only one referenced by two different names we must do what we mentioned above, let's see the following example:
list_1 = [1, 2, 3, 4, 5]
list_2 = [1, 2, 3, 4, 5]
In this case, we can notice how when using the == operator, python tells us that the value of both objects is the same, but when using the is operator, python tells us that they are different objects, which we can check by querying the id of both objects and see that they are different.
Exception
This is not an exception itself, but the tuples have a particular behavior, because these are immutable and do not have methods to change their value, however by containing elements of any type/class, these elements could be mutable or immutable objects, which means that the tuple itself does not change, but its mutable values can, which is why when we create two names and assign them an empty tuple, this not containing elements, is created by python as a single object referenced by two names, for example:
tuple_1 = ()
tuple_2 = ()
In this case, both tuple_1 and tuple_2, are pointing to the same object () which, being immutable has the same behavior as strings and integers, i.e. python creates only one object to save resources and this is pointed by tuple_1 and tuple_2.
When a tuple contains 1 or more elements, when there is the possibility that some of them are mutable objects, it behaves in memory like mutable objects, that is, even if we have two tuples with the same initial value, python will create two objects with the same value but in different memory addresses:
tuple_1 = ('Hello', 25, [1, 2, 3])
tuple_1 = ('Hello', 25, [1, 2, 3])
As we can see, a tuple can contain a list inside, which can be modified using its own methods and the tuple would still be the same immutable object:
Although it seems that we have modified the tuple, what we have actually modified is the list inside it, and as the tuple references it, it is also modified.
Knowing if an object is mutable or immutable, its id and type, is useful to us so that at the time of debugging, we realize if we are modifying the same object and in what specific object we are working, so if we do not know that this happens, we could mess up our code.
How arguments are passed to functions and what does that imply for mutable and immutable objects?
In Python, keeping in mind the mutability of objects is a great help when using functions, since they can be passed as parameters to the function by value or by reference.?
When we pass by value to a function, what we are really doing is sending the value of a variable for the function to operate with this data within the scope of the function, either to print something or return a new object if necessary, but the value of the object passed does not change, this is the case of immutable objects:
As we can see, to the change_string() function we are passing the value of s1 so that from this it creates a new object that concatenates the value referenced by s and the object "world", when we print the id of s before the new object is created, we notice that it corresponds to the same id of s1, this means that we are asking the function to work with the value allocated at that memory address, but the function does not modify it instead it tells s to now point to the new object, so now the id of s is the one corresponding to the memory address of the new object "helloworld". When we consult the object pointed by s1 it is evident that it did not change, even if we use the operator += whose utility is to ask the value of the right side to be added to the value of the left side, it does not change but it creates a new one to which s will point, because it is an immutable object passed by value:?
On the other hand, passing by reference means that what we are passing to the function is the reference of the object, that is the memory location of the object to be modified, in this case a return value is not required since the function is working on the object itself and is modifying it, in this case we are talking about mutable objects:
This example shows us that to the function change_list() we are passing by reference the list list as parameter, this passing by reference is because the objects of type/class list are mutable objects. Then the function change_list() takes the object [1, 2, 3, 4] pointed by list, and using the pointer list_1, which points to the same object, adds to the object [1, 2, 3, 4] the list [5], so instead of creating a new object with the value [1, 2, 3, 4, 5], what it does is to change the value of it. Note that the id of the object never changed, thus proving that we always worked on the same object.