Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

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.