Source: Ales Krivec on Unsplash
Immutable objects are those whose state cannot be changed after they are created. If you try to modify an immutable object, you're actually creating a new object in memory.
str
): Strings are a prime example of immutability.int
, float
, complex
): Numerical values cannot be changed in place.tuple
): Like strings, tuples are ordered sequences that cannot be modified.bool
): True
and False
are immutable.frozenset
): Immutable versions of sets.
my_string = "hello"
print(f"Original string: {my_string}, id: {id(my_string)}")
my_string = "world" # Reassignment creates a new string object
print(f"Modified string: {my_string}, id: {id(my_string)}") # Notice the different id
# Attempting to modify a string directly results in an error
# my_string[0] = 'H' # TypeError: 'str' object does not support item assignment
When you reassign my_string
, you're not changing the original "hello" string. Instead, a new string object "world" is created, and my_string
now points to this new object in memory. The id()
function confirms that the memory address has changed.
Mutable objects, on the other hand, can be modified after they are created. Changes made to a mutable object are done in place, without creating a new object.
list
): Ordered sequences that can be modified.dict
): Key-value pairs that can be changed.set
): Unordered collections of unique items that can be modified.
my_list = [1, 2, 3]
print(f"Original list: {my_list}, id: {id(my_list)}")
my_list[0] = 10 # Modifying the list in place
print(f"Modified list: {my_list}, id: {id(my_list)}") # The id remains the same
In this case, the id()
function shows that the memory address of my_list
remains the same after the modification, demonstrating that the list was changed in place.
The distinction between mutable and immutable objects has significant performance implications, especially when dealing with operations that involve repeated modifications.
Repeatedly concatenating strings using the +
operator can be inefficient because each concatenation creates a new string object. For a large number of concatenations, this can lead to excessive memory allocation and performance overhead.
import time
start_time = time.time()
result = ""
for i in range(100000):
result += str(i) # Inefficient string concatenation
end_time = time.time()
print(f"String concatenation time: {end_time - start_time:.4f} seconds")
start_time = time.time()
result_list = []
for i in range(100000):
result_list.append(str(i)) # Append to a list
result = "".join(result_list) # Join the list elements
end_time = time.time()
print(f"List append and join time: {end_time - start_time:.4f} seconds")
The output clearly demonstrates that using a mutable list to accumulate the string parts and then joining them is significantly faster than repeated string concatenation.
Understanding mutability helps you choose the right data structures for optimal performance. If you need to modify a sequence frequently, a list is generally more efficient than a tuple. If you need to ensure that data remains unchanged, a tuple is a better choice.
The concept of mutability and immutability is not unique to Python. For example, in Java:
String
class is immutable, similar to Python strings.StringBuffer
and StringBuilder
classes provide mutable alternatives for efficient string manipulation.The mutability or immutability of an object type affects how operations are performed and how memory is managed. Understanding this distinction is crucial for writing efficient, correct, and performant code.