Python: Mutable and Immutable Data Types

Python is a popular high-level programming language that supports both mutable and immutable data types. Understanding the differences between these two types is essential for efficient programming and preventing bugs.

In this blog post, we’ll explore the concepts of mutable and immutable data types in Python, their differences, and some examples.

Mutable and Immutable

The main difference between mutable and immutable data types is that mutable objects can be modified after they are created, while immutable objects cannot. In other words, the value of an immutable object cannot be changed once it is assigned, whereas the value of a mutable object can be changed.

This difference between mutable and immutable data types is important in programming because it affects how objects are stored and manipulated in memory. Mutable objects can be more efficient for certain operations because they can be modified in-place, but they can also be more error-prone if multiple variables reference the same object and one of them modifies it unexpectedly.

Immutable objects are safer in this regard because they cannot be modified once created, but they can be less efficient for some operations because they require creating new objects instead of modifying existing ones.

Immutable Data Types

As mentioned earlier, immutable data types are those whose value cannot be changed once they are created. In Python, some examples of immutable data types are:

  • Numbers (int, float, complex)
  • Strings
  • Tuples
  • Bytes

Since immutable objects cannot be modified in place, any operation that seems to modify them actually creates a new object. This means that if we assign an immutable object to multiple variables, any change made to one of them will create a new object and the other variables will still refer to the original object.

For Example:

x = 5
y = x
x += 1
print(x) # 6
print(y) # 5

In this example, we assign the value 5 to x and then assign x to y . Since int is an immutable type, both x and y reference the same object in memory. However, when we increment x by 1, a new object with value 6 is created, and x now refers to this new object. y still refers to the original object with value 5 , which was not modified.

Mutable Data Types

Mutable data types are those whose value can be changed after they are created. In Python, some examples of mutable data types are:

  • Lists
  • Dictionaries
  • Sets

Since mutable objects can be modified in place, any operation that modifies them affects the original object, and any variables that reference that object will see the updated value.

For example:

x = [1, 2, 3]
y = x
x[0] = 4
print(x) # [4, 2, 3]
print(y) # [4, 2, 3]

In this example, we assign the list [1, 2, 3] to x and then assign x to y . Since list is a mutable type, both x and y reference the same list object in memory. When we modify the first element of x to be 4 , this change affects the original list object, and both x and y now refer to the updated list object.

Best Practices

Use immutable types for keys in dictionaries

Since dictionary keys are used for lookups, using immutable types like strings or tuples can make lookups faster and avoid unexpected behavior due to changes in the keys.

d = {'key1': 'value1', (1, 2): 'value2'}

Avoid modifying objects in place

Modifying objects in place can make code harder to understand and debug. Whenever possible, use methods that create new objects instead of modifying existing ones.

new_list = [x + 1 for x in old_list] # create a new list instead of modifying the old one

Beware of unexpected changes to mutable objects

When working with mutable objects, be aware of changes that might affect other variables that reference the same object. Use the deepcopy() method to create a copy of a mutable object if you need to modify it without affecting the original.

import copy
list1 = [1, 2, 3]
list2 = list1
list1.append(4) # this also modifies list2!
list3 = copy.deepcopy(list1) # this creates a new list

dict1 = {"key1": 1, "key2": 2}
dict2 = copy.deepcopy(dict1) # creates a new dict