Reputation: 424
Suppose I have a list as such li = [1, 2, 3, 4, 5]
and a scale function as
def scale(data, factor):
for j in range(len(data)):
data[j] *= factor
Now if I pass li
to scale
function with a factor = 2
, my list gets modified. That is the actual parameter gets changed here. So if i print li
after executing the above function it gives [2, 4, 6, 8, 10]
and not the original one.
Another way scale function is implemented is as follows:-
def scale(data, factor):
for val in data:
val *= factor
Now if I execute scale(li, 2)
then it doesn't modify the actual parameter. So li
stays as [1, 2, 3, 4, 5]
.
So my question is why does one modify the list and the other doesn't?
Has this got anything to do with mutability or immutability?
Upvotes: 4
Views: 108
Reputation: 59594
data[j] *= factor
is the same as data[j] = data[j] * factor
so you are modifying data
object. This operation calls __setitem__
method of list data
, which puts the result of data[j] * factor
into the corresponding position.
When you do for val in data:
in val
is stored a reference to an item in data
. That item (object) doesn't not know anything about the list data
it was taken from. You are now working only with the item itself, not the list which contains another reference to it (along with val
).
val *= factor
creates a new object (the result of the product) and puts a reference to it into variable val
. data
still contains a reference to the old object/value which was in val
before the assignment.
See more here: https://stackoverflow.com/a/8140747/248296
Upvotes: 1
Reputation: 3036
The main difference is the fact, that you use an index to access the list in the first example and store the result in the list directly. In the second example you store the result in a variable which is holding the value taken from the list.
You can think of for val in data:
as a shorthand for:
for i in range(len(data)):
val = data[i]
Now changing val
(through val *= factor
) will not change data
(Why should it?). And as this is, what your second example is doing, the data
list is not modified.
On the other hand, when you do data[i] *= factor
, you are actually modifying the list, because you store the result in data[i]
instead of the "temporary" variable val
.
You could simplify it further, and look at
data[0] = 1
and compare it to
val = data[0]
val = 1
It should be obvious why one of them changes data
and one does not. Your loops do effectively the same things and therefor one changes data
and one does not.
Upvotes: 1
Reputation: 22294
The inplace operator...
x *= y
...is more or less equivalent to...
x = x * y
In particular, the result will be reassigned to x
.
In your first example, you assign to data[j]
, index access changes the value at index j
. This mutates your list.
In your second example, the values from the list are assigned to val
, the inline operator thus reassignes to val
. This is not an index access, so the list is not mutated.
In other words, your second example behaves like this:
for i in range(len(data)):
# values from the list are assigned to val
val = data[i]
# A new value is assigned to val
val = value * factor
Upvotes: 0
Reputation: 29071
In the second example, the for loop creates a variable val=1
(and so on). When you do val = val * factor
, the value of val changes. However, this has no connection to the original list[0]
. You are just changing a temporary variable val
On the first example however, you do list[0] = list[0] * factor
, so the assignment is done to the list itself.
I wouldn't say that it's so much a matter of mutability/immutability here (the right hand of both assignments is the same, and multiplies an immutable object in both cases). Mutability would be an issue if, eg, in the second example, you were passing val
instead of data
to your function, and change its value. In that case, the original value would not change, because val
would be immutable.
Upvotes: 1