Title: Understand mutable and immutable objects in Python
Many other programming languages have two kinds of data: value types and reference types. Value types (like integers and floats) store the item's value directly. Reference types (like collections and references to Employee objects) store references to objects where the actual data is stored.
In contrast, pretty much everything is an object in Python. Instead of thinking about whether a variable is a value or reference type, you should think about whether the data is mutable or immutable.
In brief, a mutable type can be modified after it is created but an immutable object cannot.
Immutable Objects
Python's simpler data types are generally immutable, as are a few things that are specifically designed to be immutable. Immutable objects include:
- Integers
- Floats
- Complex numbers
- Booleans
- Strings
- Tuples
- Frozen sets
- Bytes
An operation that looks like it's changing an immutable value actually creates a new value. For example, consider the following code.
i = 1337
i += 1
This code looks like it creates an integer holding the value 1337 and then adds 1 to it.
In fact, because integers are immutable, the value 1337 cannot be changed after it is created. Instead the code creates a new integer containing 1337 + 1 = 1338 and makes variable i point to it. The new value is stored with the same name, i, so it seems like i has been changed. (In a sense it has because i points to a new location in memory, but let's not quibble.)
The fact that integers are immutable is one of the reasons why Python doesn't have ++ and -- operators. Those operators could give the impression that the variable's value is changing without creating a new value. That could be misleading, so Python just dropped them.
Mutable Objects
Mutable objects include instances of any classes that you create, lists, dictionaries, sets, etc. When you change these objects, the actual object itself doesn't change, but the values it contains do. For example, if you create a list, you can add and remove items from that list. The list is the same, but the objects it contains have changed.
For another example, if you define an Author class and then make an instance, you can change that instance's annual_income property from 1.00 to 2.00. The Author object is the same, but its annual_income property has changed.
Tests
The example program performs several tests to demonstrate mutable and immutable objects. I'll walk through the program a piece at a time. You can download the example to see them all in one place.
First, the program executes this code.
print(" list2 += ['d']")
list1 = ['a', 'b', 'c']
list2 = list1
list2 += ['d']
print(f'list1: id={id(list1)}, {list1}')
print(f'list2: id={id(list2)}, {list2}')
This code creates a (mutable) list list1 and sets list2 equal to list1. That means both variables refer to the same list.
The code then adds the new value 'd' to list1. Because both variables point to the same list object, both variables see the change. The print statements produce the following result.
list2 += ['d']
list1: id=2453617396224, ['a', 'b', 'c', 'd']
list2: id=2453617396224, ['a', 'b', 'c', 'd']
You can see that the two variables have the same ID, which means they point to the same object. (Python assigns the IDs on the fly so the values you see may be different.) Notice that both variables can see the new value 'd' at the end of the list.
Next, the program executes the following code.
print(" list2 = list1 + ['d']")
list1 = ['a', 'b', 'c']
list2 = list1 + ['d']
print(f'list1: id={id(list1)}, {list1}')
print(f'list2: id={id(list2)}, {list2}')
The code creates list1. The right-hand side of the second statement, list2 = list1 + ['d'], creates a new list that equals the first list plus 'd.' It then makes variable list2 point to the new list. Here's the output.
list2 = list1 + ['d']
list1: id=2453620307840, ['a', 'b', 'c']
list2: id=2453619502784, ['a', 'b', 'c', 'd']
This time the two variables have different IDs so they point to different list objects. You can see that the first list does not contain the 'd' but the second list does.
Here's the next piece of the example.
print(" i1 = i2")
i1 = 1337
i2 = i1
print(f'i1: id={id(i1)}, {i1}')
print(f'i2: id={id(i2)}, {i2}')
This code sets integer variable i1 equal to 1337. It then sets variable i2 equal to i1 so they both point to the same variable holding the value 1337. Here's the output.
i1 = i2
i1: id=2453620025936, 1337
i2: id=2453620025936, 1337
You can see that the two variables have the same ID and the same value.
Here's the last test.
print(" i2 += 1")
i1 = 1337
i2 = i1
i2 += 1
print(f'i1: id={id(i1)}, {i1}')
print(f'i2: id={id(i2)}, {i2}')
This code again sets i1 equal to 1337 and sets i2 equal to i1. It then increments i2. Because integers are immutable, that actually creates a new integer holding the value 1337 + 1 = 1338. The program then makes i2 point to the new value.
Here's the output.
i2 += 1
i1: id=2453620025936, 1337
i2: id=2453617424240, 1338
You can see that the two variables have different IDs and different values.
Conclusion
Objects in Python can be mutable or immutable. The difference determines whether two variables pointing to the same value are updated together when one changes. If the value is mutable, then changes to one variable will be seen by the other variable. If the value is immutable, then changes to a value always create a new value so the variables will show different results.
One of the more confusing cases is demonstrated by the statement list2 += ['d'] versus list2 = list1 + ['d']. The first adds the value 'd' to the end of a list, while the second creates a new list containing list1 plus the value 'd.' If you find it confusing, you might want to use list1.append instead of += because list1.append makes it clear that you're not creating a new list.
Download the example to experiment with it and to see additional details.
|