Avoiding Mutability-Related Gotchas: Aliasing & Mutating Arguments
00:00 You may be tempted to think that mutable data types are better than immutable ones. In fact, you may need more memory requirements with immutable objects because every time you need to make a change, you have to create a new object.
00:14 This is not the case with mutable data types since when you make a change, you keep the same object. However, the logic of your computer program may be harder to follow if you have a lot of mutable objects because you need to track the state of those objects and how they’re changing.
00:32
Whereas with immutable objects, that is not an issue. But there are other pitfalls you need to be aware of when dealing with mutable objects. Let’s create a number. number = 42
, and you can create another variable, another_number
which is equal to number
.
00:48
You’ve seen earlier in the course that there’s a difference between the variable name and the object. The variable name refers to the object so when you say another_number = number
, you are creating a new name, another_number
that points to the same object that number
refers to.
01:07
You can confirm this either by looking for or fetching the identity of number
and another_number
, they’re the same number, which means that they’re the same object.
01:19
Or you can also use the is
keyword. is
checks whether two objects are the same, not whether they’re equal, that’s ==
. is
returns True
if the two objects are exactly the same object. This means that number
and another_number
are two variables referring to the same object.
01:40
How about if you had to increment number
by one somewhere in your code? number
is of course, now 43. How about another_number
? Will it be 43 as well?
01:53
Because after all, it’s pointing to the same object as number
, or will it remain 42? And it remains 42 because number
and another_number
refer to an immutable object.
02:06
42 is an integer, which is immutable. Therefore, when you increment number
, you are reassigning the name number
to a new object, the object 43.
02:16
Therefore number
now points to a new object, whereas another_number
still points to the same object as before. However, if you had something similar with a mutable type, for example, let’s assume you have a list that has only one item in it, apple
.
02:34
Then you have another fruits
, let’s say more_fruits
, which sounds better. more_fruits
is equal to fruits
. So I’m creating an alias.
02:45
more_fruits
points to the same object as fruits
. We can confirm it by saying fruits is more_fruits
, and if they are the same object, we should get True
, they are the same object.
02:59
How about if we now take fruits
and use +=
as you did earlier, and in this case, you’re going to add banana
,
03:07
so you’re adding banana
to the list fruits
, but let’s check fruits
is what you expect it to be.
03:15
fruits
is pointing to the same object so even though the items in the list have changed, the object hasn’t. The list that used to have apple
is now the same object that also has banana
.
03:29
Therefore, more_fruits
has also changed because fruits
and more_fruits
still point to the same object.
03:40
So let’s look at another pitfall you need to be aware of when dealing with mutable types. Let’s create function squares_of()
which accepts numbers
, and you can create a for
loop using the enumerate()
built-in function.
03:54
So for i, number in enumerate(numbers):
what this allows you to do is to loop through numbers
, but also have access to the indices 0, 1, 2,
04:08
and you’re going to take the i
index. So 0
if you’re in the first loop of the iteration, 1
if you’re in the second, and so on, and change it to whatever number
is.
04:22
number
is the variable you’re using in the for
loop to the power of 2. And finally, you return numbers
, the new list, I’ll stress what new means later on. Let’s create sample
,
04:36
[2, 3, 4]
, for example. So that’s my list, and you can now call squares_of(sample)
.
04:46
And as you would expect, it gives you [4, 9, 16]
. It returns a list with the squares of the numbers. Great, what could go wrong with this? But let’s have a look at sample
now.
04:58
sample
has also changed. Why? Because in the function, even though a function is self-contained, when you create new variables inside the function, they’re local to that function.
05:10
numbers
is not a new variable. numbers
is the object you’re passing to the function. It’s a list, therefore it’s mutable. So when in the for
loop, you’re changing the items in numbers
, you are mutating the same object, you’re changing the same list.
05:28
And therefore that’s not only returning numbers
as the value returned by the function, it’s also changing the original object. This is dangerous because often this is not the intended behavior.
05:43 The ideal approach in this case is not to mutate the arguments in your function. So the function may look something like this.
05:52
Define squares()
. You can then create a new list result
06:00
and a standard for
loop for number in numbers:
.
06:07
You can now append()
number
to the power of 2 to this new list. This new list is being created inside the function. Therefore it’s local to the function, but you can return result
.
06:23
You can also use a list comprehension in this case, if you prefer, you can try it out. Let’s go back to sample
, which is the list [2, 3, 4]
. squares_of(sample)
gives you the right output [4, 9, 16]
, but the original list sample
is unchanged.
06:44 You did not change the values of the original list, you created a new object with the squares of the values.
Become a Member to join the conversation.