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)