Mutability defines whether an object can be changed without changing its identifier (address in memory):
Impact on functions:
Example:
def f(lst): lst.append(42) data = [] f(data) print(data) # [42] def f2(x): x += 1 n = 1 f2(n) print(n) # 1
"What will the following code output?"
def foo(bar=[]): bar.append(1) return bar print(foo()) print(foo())
Answer: It will output:
[1]
[1, 1]
Because function arguments are initialized once at the time of definition, not on each call. Lists (and other mutable objects) in function arguments are a common trap.
Story
In a REST API, a list was returned via a function with a default parameter:
def get_default_items(items=[]): items.append('x') return items
After several calls, it was found that the list was growing, although it was expected to receive only one element.
Story
In a function, it was intended to "replace" a string:
def replace_word(word): word.replace('a', 'b') word = 'data' replace_word(word) print(word) # Expected 'dbtb', got 'data'
Str methods do not change the original string but return a new one, yet the value of the returned result was ignored.
Story
When working with nested structures:
original = [[1, 2], [3, 4]] copy = original[:] copy[0][0] = -1 print(original) # [[-1, 2], [3, 4]]
Shallow copying was used, thinking that only the copy was changed, but the nested objects remained shared.