4

If I have the following list:

a = [1, 2, 3]

And I run the following code:

for x in a:
  x += 1

It seems that this does not change the list a.

However, if I do the following:

for i in range(0, len(a)):
  a[i] += 1

This will modify the content of 'a'.

So I guess x and a[i] are referring to elements of a in a different way. What exactly is causing the difference? How are they each referring to elements of a?

bicycle
  • 8,315
  • 9
  • 52
  • 72
Enzo
  • 969
  • 1
  • 8
  • 23
  • It's worth noting looping by index like that is generally a really bad idea, if you want to modify each element of a list, consider a [list comprehension](http://www.youtube.com/watch?v=pShL9DCSIUw) instead. (This does create a new list, but that's rarely a problem, and it's trivial to push the modifications back to the original list if needs be.) – Gareth Latty Apr 17 '13 at 00:56
  • [This visualisation](http://www.pythontutor.com/visualize.html#code=a+%3D+[1,+2,+3]%0A%0Afor+x+in+a%3A%0A++x+%2B%3D+1%0A++++%0Afor+i+in+range%280,+len%28a%29%29%3A%0A++a[i]+%2B%3D+1&mode=display&cumulative=false&heapPrimitives=false&drawParentPointers=false&textReferences=false&py=3&curInstr=0) should also give some insight. – Gareth Latty Apr 17 '13 at 01:00
  • Down-voting since the question is obviously a duplicate. – martineau Apr 17 '13 at 01:11
  • @Lattyware Hi, thanks for the comment! I am wondering why exactly is this a really bad idea? Is this slower than a list comprehension? – Enzo Apr 17 '13 at 13:28
  • @Enzo It's slower, less readable, and only works with lists, not generic iterators (which means you can't take advantage of Python's lazy generators, for example). In general, iterating by index is not the best way to solve a problem in Python. – Gareth Latty Apr 17 '13 at 14:11
  • @Lattyware That's good to know. Thanks! What would you suggest me to learn more about python if I have a C background? – Enzo Apr 17 '13 at 14:32
  • @Enzo There are lots of resources out there, but I don't know of any that cater to existing programmers, with the aim of teach good practices in Python that are unusual compared to lower-level languages. I'm actually partway through constructing something for that exact audience - unfortunately not done now, however. In general, it's about applying [The Zen Of Python](http://www.python.org/dev/peps/pep-0020/) to everything you write. – Gareth Latty Apr 17 '13 at 15:45
  • @Lattyware Thank you for the advice! I would love to read your work when it's ready! – Enzo Apr 18 '13 at 00:50

4 Answers4

5

When you iterate over a list, each element is yielded, in turn. However, there are different kinds of objects. mutable and immutable. When you do something like:

a += 1

with an immutable object, it translates roughly to:

a = a + 1

Now in this case, you take the object reference by a, add 1 to it to create a new object. Then you assign that new object the name a. Notice how if we do this while iterating, we don't touch the list at all -- We only keep creating new objects and assigning them to the name a.

This is different for a mutable object. Then a += 1 actually changes the object in place. So, the list will see the change because the object that it is holding has changed (mutated). (With the immutable object the object contained in the list wasn't changed because it couldn't be). See this question for more info.

This also makes it a little more clear what's happening when you're iterating by indices. you construct a new integer and you put it in the list (forgetting whatever was in that slot before).

Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • +1, actually answers the question and mentions the key point that *names* and *objects* are separate. – Gareth Latty Apr 17 '13 at 00:59
  • @Lattyware -- Yeah. I'm still not positive I like the semantics of names/objects since a name on the right hand side acts like an object ... It gets muddy no matter how you want to think of it. But ... this is a tricky question and even though it's been asked before, I figure that the more people who try to explain it, the more likely we are to have an answer which resonates with a particular user. – mgilson Apr 17 '13 at 01:03
  • The trick is that assignment is a special case. when you assign a value you say 'this name now means the thing on the right', rather than 'this thing becomes equal to this thing on the right', which is how most people think of it. It's why `=` is actually a really rubbish assignment operator (in some ways - it makes code read naturally, but gives people preconceptions). I've always been a fan of `:` for assignment, personally. – Gareth Latty Apr 17 '13 at 01:05
  • And it becomes more clear when you realize that "names" are used to look the object up in the locals or globals dictionary. Then assignment to a "name" really just becomes inserting or replacing a key/value in the appropriate dict. – mgilson Apr 17 '13 at 01:09
3

When you say,

for x in [1,2,3]:
    x+=1

You are saying, temporarily save x as a variable, and add one to that temporary save. When you get to the next iteration, the garbage man destroys that variable because it was temporary. x is not the spot in the list. It is the value of that spot in the list.

Edit: when I say that it gets deleted, I was not being clear with my words. What happens is that each time through the loop, x is replaced with another value, and so what happened before goes away (unless you did something else with it). With the operations you are using, though, you are not changing the values of any elements in the list. My bad for the confusion.

When you do it the other way,

for x in range(len(lst)):
    lst[x] += 1

Then you are talking about the list's values. x is the index of the variable, and can therefore alter the value of the list's value at that spot.

erdekhayser
  • 6,537
  • 2
  • 37
  • 69
  • I would be careful saying that it gets *destroyed*. eg. `>>> for x in (1, 2, 3): x += 1` `>>> print x` `4` (I know what you mean though) – jamylak Apr 17 '13 at 01:19
2

The concept that matters here is the idea of referencing. In Python, variables are references to objects that sit somewhere in memory. Let's use the an arrow → to indicate a reference. Variable → Object. Variable on the left, object on the right.

The array can be visualized as three variables that reference three integer objects.

a[0] → int(1)
a[1] → int(2)
a[2] → int(3)

Now, integer objects are immutable. They can't be changed. When you change an integer variable you're not changing the object the variable refers to. You can't, because ints are immutable. What you can do is make the variable reference a different object.

Direct update

Let's look at the second loop first since it's simpler. What happens if you update the array directly?

for i in range(0, len(a)):
  a[i] += 1

First let's unroll the loop:

a[0] += 1
a[1] += 1
a[2] += 1

For integers, a[0] += 1 is equivalent to a[0] = a[0] + 1. First, Python evaluates a[0] + 1 and gets the result int(2). Then it changes a[0] to reference int(2). The second and third statements are evaluated similarly.

a = [1, 2, 3]  # a[0] → int(1)
               # a[1] → int(2)
               # a[2] → int(3)

a[0] += 1      # a[0] → int(2)
a[1] += 1      # a[1] → int(3)
a[2] += 1      # a[2] → int(4)

Indirect update

And what about what I'll call "indirect" updating?

for x in a:
  x += 1

Unrolling the loop yields this equivalent series of statements:

x = a[0]
x += 1
x = a[1]
x += 1
x = a[2]
x += 1

What happens at each step, and why isn't the array changed?

x = a[0]

This makes x reference whatever object a[0] references. Both a[0] and x reference the same int(1) object, but x isn't directly connected to a[0]. It refers to whatever a[0] refers to, not to a[0] itself.

x += 1

This changes what x refers to. It has no effect on a[0].

The same thing happens for the second and third assignments. The result is that x is continually changed while the elements of a are merely read from but never modified. And so when the loop terminates a is unchanged.

a = [1, 2, 3]  # a[0] → int(1)
               # a[1] → int(2)
               # a[2] → int(3)

x = a[0]       # x → int(1)
x += 1         # x → int(2)
x = a[1]       # x → int(2)
x += 1         # x → int(3)
x = a[2]       # x → int(3)
x += 1         # x → int(4)
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
1

Think of it as in your first loop, x is just a substitute for every element in a. It's not the actual element in a (although by id() it is because they both reference the same object). When you're doing x += 1, you're just changing the value of x and not the value in the list.

In your second for-loop, you're actually modifying the list by doing a[i] += 1.

TerryA
  • 58,805
  • 11
  • 114
  • 143