Mutability and Immutability in Python — Let’s Break It Down

In the second example above, we used a capital S in variable b’s object.

That’s why it was False.

Let’s look at more examples.

>>> a = 256>>> b = 256>>> id(a) == id(b)True>>> a = 260>>> b = 260>>> id(a) == id(b)FalseWhy is it True for 256 but False for 260?The reason is that Python keeps an array of integer objects for all integers between -5 and 256.

When you create an integer in that range, you get back a reference to the already existing object.

It does this with two macros, NSMALLNEGINTS and NSMALLPOSINTS.

If the value ival satisfies the condition of being between -5 and 256, the function get_small_int is called.

#ifndef NSMALLPOSINTS#define NSMALLPOSINTS 257#endif#ifndef NSMALLNEGINTS#define NSMALLNEGINTS 5#endif#define CHECK_SMALL_INT(ival) do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { return get_small_int((sdigit)ival); } while(0)Note: You can also use “is” to check if two variables have the same object id.

>>> list1 = [1, 2, 3]>>> list2 = list1>>> list1 is list2TrueLet’s introduce some more terminology.

The same list above has two different names, list1 and list, and we can say that it is aliased.

Because variables refer to objects, if we assign one variable to another, both variables refer to the same object.

That is what aliasing means.

Let’s also talk about cloning.

If we want to modify a list and also keep a copy of the original, we need to make a copy of the list.

This process is called cloning.

Taking any slice of a list creates a new list.

>>> list1 = [1, 2, 3]>>> list2 = list1[:]>>> list1[1, 2, 3]>>> list2[1, 2, 3]>>> list1 is list2False>>> id(list1) == id(list2)TrueTypesWhat is type?type()In Python, all data is stored in the form of an object.

An object has three things: id, type, and value.

The type function will provide the type of the object that’s provided as its argument.

>>> type(2)<class 'int'>>>> type(-6.

25)<class 'float'>>>> type(2,)<class 'tuple'>>>> type("hello")<class 'str'>>>> type('A')<class 'str'>>>> type('346.

789')<class 'str'>>>> type([2, 3, 4])<class 'list'>>>> type({'category': 'produce', 'count': 200})<class 'dict'>>>> type(print)<class 'builtin_function_or_method'>>>> type(type)<class 'type'>Like id(), type() is also useful for debugging.

What are mutable objects?PythonMutable objects:list, dict, setA mutable object is a changeable object and its state can be modified after it is created.

>>> my_list = ['cat', 'dog', 'bunny']>>> my_list['cat', 'dog', 'bunny']>>> print('Address of my_list is: {}'.

format(id(my_list)))Address of my_list is: 139929780579208If we want to change the first value in our list and print it out, we can see that the list changed, but the memory address of the list is the same.

It changed the value in place.

That’s what mutable means.

>>> my_list[0] = 'sugar glider'>>> my_list['sugar glider', 'dog', 'bunny']>>> print('Address of my_list is: {}'.

format(id(my_list)))Address of my_list is: 139929780579208Let’s look now at the memory addresses of values of the list and see what happens before and after we change the value of the first element of the list.

>>> my_list['sugar glider', 'dog, 'bunny']>>> id(my_list)139929780579208>>> my_list[0]'sugar glider'>>> id(my_list[0])139905997708792>>> my_list[0] = 'rabbit'>>> id(my_list[0])139905997708400>>> id(my_list)139929780579208The id of my_list[0] is 139905997708792 when the value of the first element is ‘sugar glider’.

The id of my_list[0] is 139905997708400 after we change the value to ‘rabbit.

’ Notice they are two different ids.

When we modify a list and change its values in place, the list keeps the same address.

However, the address of the value that you changed will have a different address.

The id of my_list still remained the same at 139929780579208.

What are immutable objects?PythonImmutable objects:integer, float, string, tupleAn immutable object is an object that is not changeable and its state cannot be modified after it is created.

In Python, a string is immutable.

You cannot overwrite the values of immutable objects.

However, you can assign the variable again.

>>> phrase = 'how you like me now'>>> phrasehow you like me now>>> phrase = 'do you feel lucky'>>> phrasedo you feel luckyIt’s not modifying the string object; it’s creating a new string object.

To see this in more detail, we can utilize the id function that we learned earlier.

Recall that id() function prints out the memory address.

>>> phrase = 'how you like me now'>>> print('Address of phrase is: {}'.

format(id(phrase)))Address of phrase is: 139929793080104>>> phrase = 'do you feel lucky'>>> print('Address of phrase is: {}'.

format(id(phrase)))Address of phrase is: 139929792606832Since a string is immutable, it created a new string object.

The memory addresses do not match.

Let’s try to change a single character of the phrase.

>>> phrase[0] = 'D'.

TypeError: 'str' object does not support item assignmentWe get a TypeError because strings are immutable.

We can’t change the string object.

Let’s also talk about tuples.

Immutability on tuples is only partly true.

The tuple itself cannot be modified, but objects referenced by the tuple can be modified.

Sometimes called “non-transitive immutability,” if the tuple has an immutable field like a string, then it cannot be modified.

But a mutable field like a list can be edited, even if it’s embedded in the “immutable” tuple.

Why do mutable and immutable objects matter and how differently does Python treat them?Numbers, strings, and tuples are immutable.

Lists, dictionaries, and sets are mutable, as are most new objects you’ll code with classes.

Immutability may be used to ensure that an object remains constant throughout your program.

The values of mutable objects can be changed at any time and place, whether you expect it or not.

You can change a single value of a mutable data type and it won’t change its memory address.

However, you can’t change a single value of an immutable type.

It will throw an error.

How are arguments passed to functions and what does that imply for mutable and immutable objects?The way that the Python compiler handles function arguments has to do with whether the objects in the function arguments are mutable or not immutable.

If a mutable object is called by reference in a function, the original variable may be changed.

If you want to avoid changing the original variable, you need to copy it to another variable.

When immutable objects are called by reference in a function, its value cannot be changed.

Let’s look at this Python script and guess what it will print:def increment(n): n += 1b = 9increment(b)print(b)The answer: 9Reason: The variable b refers to the object with value 9.

When we pass b as a function argument to increment(n) function, the local variable n refers to the same object.

However, integers are immutable so we need to create a new object with the value 10 and assign it to the variable n.

The variable n is pointing to a different object from what b is pointing.

Now, n refers to an object with value 10, but b still refers to an object with value 9.

When we print(b), we get the answer 9.

Let’s look at another Python script and guess what it will print:def increment(n): n.

append(4)my_list = [1, 2, 3]increment(my_list)print(my_list)The answer: [1, 2, 3, 4]Reason: The variable my_list refers to a list object that contains references to three integers.

Lists are mutable but integers are immutable.

When we pass my_list as a function argument to increment(n) function, the function has the local variable n refer to the same object that my_list refers.

Since lists are mutable, the .

append() method is able to modify the list in place.

No new object is created and when we print my_list, we get the answer [1, 2, 3, 4].

Let’s look at another Python script to understand more about function parameters and why mutability and immutability matter.

def assign_value(n, v): n = vlist1 = [1, 2, 3]list2 = [4, 5, 6]assign_value(list1, list2)print(list1)This will print: [1, 2, 3]Reason: We pass both lists as function parameters to the assign_value(n, v) function.

The function has the local variable n refer to the same object that list1 refers, and the local variable v refers to the same object that list2 refers.

The function body reassigns n to what v is referring.

Now n and v are referring to the same object.

The variables n, v, and list2 all point to the list object [4, 5, 6], while list1 still points to the list object [1 2, 3].

This is why when we print list1, we get the answer: [1, 2, 3]How do we write a function that returns a copy of a list?.Here’s one way of doing it.

Let’s look at copy_list(l) function.

def copy_list(l): return l[:] my_list = [1, 2, 3]new_list = copy_list(my_list)We pass my_list as a function parameter to the copy_list(l) function.

The function has the local variable l refer to the same object that my_list refers.

When we use the slice operation [:], it creates a copy of a list and when we return that copy, we are returning the reference to that copy.

Now, new_list refers to a different object than what my_list refers.

>>> def copy_list(l):.

return l[:].

>>> my_list = [1, 2, 3]>>> new_list = copy_list(my_list)>>> my_list[1, 2, 3]>>> copy_list[1, 2, 3]>>> my_list == copy_listTrue>>> my_list is copy_listFalse>>> id(my_list) == id(copy_list)FalseI know we covered a lot here, so take a deep breath, practice, repeat, and you’ll soon be able to explain these concepts to other people.

Thanks for reading!.. More details

Leave a Reply