0

Why will the following code increment each number:

numbers = [1, 2, 3]

for number in numbers:
    numbers[numbers.index(number)] += 100

print(numbers) # prints [101, 201, 301]

but this will not?:

numbers = [1, 2, 3]

for number in numbers:
    number += 100

print(numbers) # prints [1, 2, 3]

Adding to the confusion, we seem to be dealing with same object in both cases:

numbers = [1, 2, 3]

for number in numbers:
    print(numbers[numbers.index(number)] is number) # prints True

I understand this has "something" to do with assigning vs mutating a value and/or memory allocation, but I cannot put my finger on what exactly is happening.

barciewicz
  • 3,511
  • 6
  • 32
  • 72

2 Answers2

2

numbers[x] is just a different syntax for numbers.__getitem__(x) (when used as an rvalue) or numbers.__setitem__(x, y) (when used as an lvalue).

So in your first example, the __setitem__() method of the numbers lists is called to update the list. Under the hood your code is roughly translated to something like this:

for number in numbers:
    index = numbers.index(number)
    new_value = numbers.__getitem__(index) + 100
    numbers.__setitem__(index, new_value)

Note that above code is valid Python and has the same effect as your original loop.

In your second example, the values are retrieved from the list and stored in a local variable number, which you then overwrite. This has no effect on the original list.


NB: Also note that comparing integers using is may lead to unexpected behaviour. See for example this SO post for more details about that.

wovano
  • 4,543
  • 5
  • 22
  • 49
1

Think of it as thus:

for number in numbers:

is roughly the same as:

for i in range(len(numbers)):
    number = numbers[i]

Now, in the second case it becomes more obvious that adding

    # number += 100  # which is not a mutation, but a rebinding equivalent to
    number = number + 100 

simply reassigns the variable number and that numbers[i] is and should be unaffected. This holds for the first loop variant, too.

user2390182
  • 72,016
  • 6
  • 67
  • 89
  • Addition: if possible, then `__iadd__`/`+=` is implemented such that it mutates the object. See lists, where `+=` is equivalent to `extend`. – timgeb Sep 15 '21 at 09:00