4

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?

saurav
  • 427
  • 4
  • 14
  • Val in that for loop is immutable – Josef Korbel Aug 23 '18 at 13:07
  • In first example list is passed as reference by default thusunderline data is modified. Whereas val is the local variable which has been updated locally for each value in the list. Does not affecting the original list. – Forever Learner Aug 23 '18 at 13:09

4 Answers4

1

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.

Leon
  • 2,926
  • 1
  • 25
  • 34
1

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.

blue_note
  • 27,712
  • 9
  • 72
  • 90
1

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

warvariuc
  • 57,116
  • 41
  • 173
  • 227
  • In the link that you shared he has written that for mutable objects, both references will be same. Is it always so? Just a newbie in python.. So curious :) – saurav Aug 23 '18 at 15:22
  • 1
    "both references will be same" -- he said "both references refer to the same object" Mutability is a matter of protocol. An object is mutable if any of its methods modifies the object in place, no new object is created. If all the methods return a new object, the object is immutable. – warvariuc Aug 23 '18 at 16:19
  • so if i have a list as l1 = [1, 2, 3] and l2 = [1, 2, 3] then does that mean that both references l1 and l2 will point to same object since lists are mutable? Sorry to disturb you again, just clarifying my concepts. – saurav Aug 24 '18 at 09:48
  • no, these are two lists which are equal but are different objects (`id(l1) != id(l2)`). – warvariuc Aug 24 '18 at 11:45
0

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
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73