Python tutorials > Core Python Fundamentals > Data Types and Variables > What is mutability vs immutability?

What is mutability vs immutability?

In Python, understanding the difference between mutability and immutability is crucial for writing efficient and bug-free code. Mutability refers to the ability of an object to be changed after it is created, while immutability means that an object's state cannot be altered once it is created. This tutorial explores these concepts with examples.

Introduction to Mutability and Immutability

Immutability: An immutable object's value cannot be changed after it's created. If you try to modify it, a new object is created instead. Examples include integers, floats, strings, tuples, and frozensets.

Mutability: A mutable object's value can be changed after it's created. The object's identity remains the same. Examples include lists, dictionaries, and sets.

Immutability in Action: Strings

Strings are immutable. When you concatenate strings, you're not modifying the original string; instead, you're creating a new string object.

In the code, id() returns the unique identifier of an object. You'll notice that the id of my_string changes after concatenation, indicating a new object was created.

my_string = "Hello"
print(id(my_string))

my_string = my_string + " World"
print(id(my_string))

Mutability in Action: Lists

Lists are mutable. You can add, remove, or modify elements within a list without changing its identity.

In the code, the id() of my_list remains the same after appending an element, confirming that the original list object was modified.

my_list = [1, 2, 3]
print(id(my_list))

my_list.append(4)
print(id(my_list))

print(my_list)

Tuples: Immutable Sequences

Tuples are immutable sequences. You cannot modify their elements after creation. While you can create a new tuple by concatenating, the original tuple remains unchanged.

The commented-out line demonstrates that directly modifying a tuple element results in a TypeError.

my_tuple = (1, 2, 3)

# Attempting to modify a tuple will raise an error:
# my_tuple[0] = 4  # This will cause a TypeError

new_tuple = my_tuple + (4,)
print(new_tuple)

Dictionaries: Mutable Key-Value Pairs

Dictionaries are mutable. You can add, remove, or modify key-value pairs without changing the dictionary's identity.

The id() of my_dict remains the same after adding a new key-value pair, indicating that the original dictionary was modified.

my_dict = {'a': 1, 'b': 2}
print(id(my_dict))

my_dict['c'] = 3
print(id(my_dict))

print(my_dict)

Concepts behind the snippet

Understanding mutability and immutability is fundamental to understanding how Python handles data. Mutable objects can be changed in place, which can be more efficient in terms of memory usage and speed when performing many operations. However, it also means that multiple variables pointing to the same mutable object will all be affected by changes to that object. Immutable objects guarantee that their value will never change, leading to simpler reasoning about code and better thread safety.

Real-Life Use Case Section

Consider a scenario where you're working on a multi-threaded application. If multiple threads access and modify a mutable object (like a list) simultaneously, it can lead to race conditions and unpredictable behavior. Using immutable data structures (like tuples or frozensets) in such cases ensures thread safety, as their values cannot be changed concurrently.

Another example is caching. Immutable objects can be safely cached without worrying about them being modified unexpectedly.

Best Practices

  • Use immutable data structures when data integrity is paramount.
  • Be cautious when passing mutable objects as default arguments to functions, as the default argument is only evaluated once, creating a single mutable object that is shared across all calls to the function.
  • When copying mutable objects, use copy.copy() (shallow copy) or copy.deepcopy() (deep copy) to avoid unintended modifications to the original object.

Interview Tip

During interviews, be prepared to explain the difference between mutable and immutable data types in Python, provide examples of each, and discuss the implications for memory management and program behavior. You should also be able to explain how to copy mutable objects to avoid unintended side effects.

When to use them

  • Use mutable objects when you need to frequently modify data in place, and when you are sure that unintended side effects won't cause issues. Examples include building up lists or dictionaries in a loop.
  • Use immutable objects when you need to ensure data integrity, when you need thread safety, or when you want to simplify reasoning about your code. Examples include representing configurations, database records, or mathematical constants.

Memory footprint

Mutable objects, when modified in place, generally have a smaller memory footprint compared to creating new immutable objects for each modification. However, the trade-off is the potential for unexpected side effects and the need for careful management.

Immutable objects may lead to higher memory consumption due to the creation of new objects, but they offer greater predictability and ease of debugging.

Alternatives

For mutable lists, there are immutable alternatives like tuples. Instead of directly modifying a list, consider creating a new one. For sets, there are frozensets which are immutable. For dictionaries, although there isn't a direct immutable equivalent, you can simulate immutability by creating a new dictionary each time you want to 'modify' it, or use specialized libraries that offer immutable dictionary implementations (though these are less common).

Pros of Immutability

  • Thread Safety: Immutable objects are inherently thread-safe as they cannot be modified after creation, eliminating the risk of race conditions.
  • Simpler Reasoning: Code is easier to reason about and debug as the state of immutable objects is predictable.
  • Caching: Immutable objects are excellent candidates for caching as their values will not change unexpectedly.

Cons of Immutability

  • Performance: Creating a new object for each modification can be less efficient than modifying an object in place, especially for large data structures.
  • Memory Consumption: Creating numerous immutable objects can lead to higher memory usage.

FAQ

  • Why are strings immutable in Python?

    Strings are immutable for several reasons, including optimization of memory allocation and improved performance. Since strings are often used as keys in dictionaries, immutability guarantees that their hash values remain constant.

  • How can I modify a tuple?

    You cannot directly modify a tuple. However, you can create a new tuple based on an existing one using concatenation or slicing.

  • What is the difference between `copy.copy()` and `copy.deepcopy()`?

    copy.copy() creates a shallow copy, which means it creates a new object but references the same internal objects as the original. copy.deepcopy() creates a deep copy, which means it creates a new object and recursively copies all the internal objects as well. For mutable objects containing other mutable objects, deepcopy() is often preferred to avoid unintended side effects.