Today I have a short drill for you. It’s about passing mutable and immutable objects as arguments to functions.
If you prefer to watch the video version, here it is:
Here’s the problem:
Here’s a function that takes two arguments and reassigns the first of them to 10 and the first element of the second argument (which is a list) also to 10:
>>> def func(num, lst):
... num = 10
... lst[0] = 10
...
Here we have an integer and a list:
>>> a = 5
>>> b = [5, 5]
We pass the integer and the list as arguments to our function:
>>> func(a, b)
The question is: what are a and b after the function returns?
Now you can stop reading and try to figure it out for yourself.
And here’s the solution:
>>> a, b
(5, [10, 5])
So, a is unchanged, whereas the first element of b is changed.
Explanation
When we pass an argument to a function, we assign an object to the name of the parameter. So, when we pass a and b to func, we assign a to num and b to lst.
If we then assign a new value to num in the function, we’re actually creating a new object in the local scope of the function. From now on num will reference this new object, but the original object in global scope will not be changed. This is why a is still 5 after the function returns. This is how it works with immutable objects.
The second argument, lst, is assigned the b list. A list is a mutable object. Inside the function the list is modified and as this is a mutable type, this is an in-place change. Both lst and b share a reference to the same list object, so if the list is modified either in the function or in global scope, the change is visible everywhere.
I have a post on shared references, so feel free to read it if you like.
If instead of modifying an element in the list we assigned a different list to lst in the function, the result would be just like with the first argument. Then we would create a new object in the local scope of the function and from that point on lst would reference the new object and have no effect on the b list in global scope. Let’s try to do it now:
>>> def func(num, lst):
... num = 10
... lst = [10, 5]
...
>>> a = 5
>>> b = [5, 5]
>>>
>>> func(a, b)
>>>
>>> a, b
(5, [5, 5])
As you can see, now the list in the function and the list in global scope are two different objects.