= [1,2,3,4,5]
a 10)
a.append(print(a)
[1, 2, 3, 4, 5, 10]
Programming for Data Science
A mutable object is a data structure whose internal values can be changed.
For example, tuples are immutable, lists are not. That is, lists are mutable.
Let’s see how this works in practice.
Here, we mutate a list by appending a value to it.
= [1,2,3,4,5]
a 10)
a.append(print(a)
[1, 2, 3, 4, 5, 10]
0] = 5
a[print(a)
[5, 2, 3, 4, 5, 10]
If we try the same things with a tuple, we get an error.
= (1,2,3,4,5)
b 10)
b.append(print(b)
AttributeError: 'tuple' object has no attribute 'append'
0] = 5
b[print(b)
TypeError: 'tuple' object does not support item assignment
This, on the other hand, is not mutation:
= [1,2,3,4,5,10] # A list
a = (1,2,3,4,5,10) # A tuple
b print(a)
print(b)
[1, 2, 3, 4, 5, 10]
(1, 2, 3, 4, 5, 10)
We are just re-assigning a new value to the variable.
The new value just replaces the old one.
In mutation, the same data structure remains in place but its contents are changed.
Note, however, that this works with tuples:
+= (11,)
b print(b)
(1, 2, 3, 4, 5, 10, 11)
It looks like mutation, but it’s not.
This is because we are replacing b
with a new tuple value.
Relatedly, mutable and immutable objects behave differently in the context of variable assignment.
For example, when you assign a variable to another variable of a mutable datatype, any changes to the data are reflected by both variables.
The new variable is just an alias for the old variable.
This is only true for mutable datatypes.
Lets explore how +
operator behaves differently between mutables and immutables.
First, let’s create a function that will allow us to compare the objects as we modify them.
def compare_objects(trial:int, obj1:str, obj2:str):
= eval(obj1)
o1 = eval(obj2)
o2 print(f"t{trial} {obj1} {o1} {id(o1)}")
print(f"t{trial} {obj2} {o2} {id(o2)}")
print(f"{obj1} == {obj2}:", o1 == o2)
List t1
We initialize a list and make a copy of it.
Note that the two variables share the same id
.
= [1,2,3,4,5]
a0 = a0 # Make a copy of a list
a1 1, 'a0', 'a1') compare_objects(
t1 a0 [1, 2, 3, 4, 5] 139842806146944
t1 a1 [1, 2, 3, 4, 5] 139842806146944
a0 == a1: True
List t2
Now we add to the copy and note the effects on the original.
The original value is also changed.
This is because both variables point to the same object.
+= [12] # Extend the copy
a1 2, 'a0', 'a1') compare_objects(
t2 a0 [1, 2, 3, 4, 5, 12] 139842806146944
t2 a1 [1, 2, 3, 4, 5, 12] 139842806146944
a0 == a1: True
List t3
Note, however, that if we don’t use the unary operator,
then a1
becomes a different object!
Lutz goes into the difference between the +=
and the +
in Ch 11 pages 360-363.
= a1 + [12] # Extend the copy
a1 3, 'a0', 'a1') compare_objects(
t3 a0 [1, 2, 3, 4, 5, 12] 139842806146944
t3 a1 [1, 2, 3, 4, 5, 12, 12] 139842804350592
a0 == a1: False
List t4
Try it with a new object copy, to avoid any possible inference between t2
and t3
.
= a0
a2 = a2 + [12] # Extend the copy
a2 4, 'a0', 'a2') compare_objects(
t4 a0 [1, 2, 3, 4, 5, 12] 139842806146944
t4 a2 [1, 2, 3, 4, 5, 12, 12] 139842804515456
a0 == a2: False
We get the same result.
Tuple t1
Let’s try this with a tuple.
We see again that both variables have the same id
.
= (1,2,3,4,5)
b0 = b0 # Make a copy of a tuple
b1 1, 'b0', 'b1') compare_objects(
t1 b0 (1, 2, 3, 4, 5) 139842804681360
t1 b1 (1, 2, 3, 4, 5) 139842804681360
b0 == b1: True
Tuple t2
However, if extend the tuple with the unary operator, b1
becomes a new object.
Note how this differs from the list behavior.
+= (12,) # Extend the copy
b1 2, 'b0', 'b1') compare_objects(
t2 b0 (1, 2, 3, 4, 5) 139842804681360
t2 b1 (1, 2, 3, 4, 5, 12) 139842804315424
b0 == b1: False
Tuple t3
If we don’t use the unary operator, the same thing happens again.
The value of b1
becomes a new object because the variable has been reassigned.
= b1 + (12,) # Extend the copy
b1 3, 'b0', 'b1') compare_objects(
t3 b0 (1, 2, 3, 4, 5) 139842804681360
t3 b1 (1, 2, 3, 4, 5, 12, 12) 139842804316096
b0 == b1: False
Let’s look at another example.
Here is a list:
= ['hi']
foo = foo
bar 1, 'foo', 'bar') compare_objects(
t1 foo ['hi'] 139842804665216
t1 bar ['hi'] 139842804665216
foo == bar: True
+= ['bye']
bar 2, 'foo', 'bar') compare_objects(
t2 foo ['hi', 'bye'] 139842804665216
t2 bar ['hi', 'bye'] 139842804665216
foo == bar: True
= bar + ['bye']
bar 2, 'foo', 'bar') compare_objects(
t2 foo ['hi', 'bye'] 139842804665216
t2 bar ['hi', 'bye', 'bye'] 139842805379520
foo == bar: False
And here is a tuple:
= ('hi')
foo1 = foo1
bar1 1, 'foo1', 'bar1') compare_objects(
t1 foo1 hi 139843849783920
t1 bar1 hi 139843849783920
foo1 == bar1: True
+= ('bye')
bar1 2, 'foo1', 'bar1') compare_objects(
t2 foo1 hi 139843849783920
t2 bar1 hibye 139842806205616
foo1 == bar1: False