Python > Core Python Basics > Data Structures > Mutability and Immutability

Understanding Mutability and Immutability in Python

This code snippet demonstrates the concepts of mutability and immutability in Python, using lists and tuples as examples. It highlights how mutable objects can be changed after creation, while immutable objects cannot.

Introduction to Mutability and Immutability

In Python, every variable holds a reference to an object. Objects can be either mutable or immutable.

Mutable objects can be changed after they are created. Examples include lists and dictionaries.

Immutable objects cannot be changed after they are created. Examples include integers, floats, strings, and tuples.

Understanding this distinction is crucial for avoiding unexpected behavior in your code, especially when dealing with shared references.

Demonstrating Mutability with Lists

This code shows how a list, which is mutable, can be modified after its creation. We use the append() method to add an element and directly assign a new value to an existing element. Each change directly affects the original list object.

my_list = [1, 2, 3]
print(f"Original list: {my_list}")

my_list.append(4)
print(f"List after appending: {my_list}")

my_list[0] = 10
print(f"List after modifying element: {my_list}")

Demonstrating Immutability with Tuples

Tuples are immutable. Attempting to modify an element of a tuple (e.g., my_tuple[0] = 10) will raise a TypeError. The code demonstrates how we can effectively "modify" a tuple by creating a brand new tuple object and assigning it to the same variable. This doesn't change the original tuple; it replaces it with a new one.

my_tuple = (1, 2, 3)
print(f"Original tuple: {my_tuple}")

# The following line will raise a TypeError
# my_tuple[0] = 10

my_tuple = (10, 2, 3) # Creating a NEW tuple
print(f"Tuple after 'modifying' by re-assignment: {my_tuple}")

Impact of Mutability on Shared References

This section illustrates the potential side effects of mutability when dealing with shared references. When list2 = list1, both variables point to the same list object in memory. Modifying list1 will therefore affect list2 as well.

To avoid this, use the .copy() method to create a new list object that contains the same elements. In the example list4 = list3.copy(), list4 and list3 are independent, so modifying one does not affect the other.

list1 = [1, 2, 3]
list2 = list1  # list2 now references the SAME list object as list1

print(f"list1: {list1}")
print(f"list2: {list2}")

list1.append(4)

print(f"list1 after appending to list1: {list1}")
print(f"list2 after appending to list1: {list2}")

list3 = [1, 2, 3]
list4 = list3.copy() # list4 is a NEW list object, a copy of list3

list3.append(4)

print(f"list3 after appending to list3: {list3}")
print(f"list4 after appending to list3: {list4}")

Real-Life Use Case: Configuration Management

Mutable Lists for Dynamic Configurations: Imagine managing server configurations. You might use a list to store allowed ports. Since the allowed ports can change, using a mutable list makes sense. You can easily add or remove ports as needed without creating a new configuration object each time.

Immutable Tuples for Secure Configurations: Consider storing cryptographic keys. Since changing a key after deployment poses a significant security risk, using a tuple ensures that the key remains constant throughout the application's lifecycle.

Best Practices

  • Be mindful of side effects: When working with mutable objects, especially in functions, be aware that changes to the object will affect the original object outside the function's scope if it's passed by reference.
  • Use immutability when appropriate: If you want to ensure that data cannot be accidentally modified, use immutable data structures like tuples or strings.
  • Copy when needed: When you want to modify a mutable object without affecting the original, create a copy using methods like .copy() for lists or copy.deepcopy() for more complex objects.

When to use them

  • Mutable objects (like lists, dictionaries, sets) are useful when you need to frequently modify the data structure in place. This avoids creating new objects repeatedly, which can improve performance in scenarios with frequent data changes.
  • Immutable objects (like tuples, strings, integers, floats) are beneficial when you need data integrity and want to ensure that the data cannot be altered after creation. They are also useful as keys in dictionaries because dictionary keys must be immutable.

Alternatives

  • For mutable lists, you could use array.array for more compact storage of numeric data, though it's less flexible with data types.
  • For immutable sequences similar to tuples, namedtuple provides named fields for better readability and maintainability.

Interview Tip

A common interview question is to explain the difference between mutable and immutable objects in Python. Be prepared to provide examples of each and explain the implications of mutability on shared references and function arguments. Mention techniques like .copy() and copy.deepcopy() to create independent copies of mutable objects.

FAQ

  • Why are strings immutable in Python?

    Strings are immutable for several reasons: Security: Immutable strings prevent unexpected modifications to sensitive data. Performance: Immutability allows for optimizations like string interning (reusing the same string object for identical string literals), which can save memory and improve performance. Hashability: Immutable objects can be used as keys in dictionaries because their hash value remains constant.
  • What is the difference between copy() and deepcopy()?

    copy() creates a shallow copy of an object, meaning it creates a new object but the elements within that object are still references to the original elements. deepcopy() creates a deep copy, meaning it creates a new object and recursively copies all the objects within it. If the original object contains mutable objects, changes to those objects in the original will be reflected in the shallow copy but not in the deep copy.