views:

190

answers:

7

I'm wondering about some details of how for ... in works in Python.

My understanding is for var in iterable on each iteration creates a variable, var, bound to the current value of iterable. So, if you do for c in cows; c = cows[whatever], but changing c within the loop does not affect the original value. However, it seems to work differently if you're assigning a value to a dictionary key.

cows=[0,1,2,3,4,5]
for c in cows:
  c+=2

#cows is now the same - [0,1,2,3,4,5]

cows=[{'cow':0},{'cow':1},{'cow':2},{'cow':3},{'cow':4},{'cow':5}]
for c in cows:
  c['cow']+=2

# cows is now [{'cow': 2}, {'cow': 3}, {'cow': 4}, {'cow': 5}, {'cow': 6}, {'cow': 7}
#so, it's changed the original, unlike the previous example

I see one can use enumerate to make the first example work, too, but that's a different story, I guess.

cows=[0,1,2,3,4,5]
for i,c in enumerate(cows):
  cows[i]+=1

# cows is now [1, 2, 3, 4, 5, 6]

Why does it affect the original list values in the second example but not the first?

[edit]

Thanks for the answers. I was looking at this from a PHP point of view, where you can use the & symbol in foreach to specify whether you are operating on a reference to or a copy of the iterable. I see now that the real difference is a basic detail of how python works regarding immutable objects.

+3  A: 

Doing a name assignment as you have in the first loop only rebinds the name. Doing an item assigment as you have in the second loop modifies the existing object.

Ignacio Vazquez-Abrams
+4  A: 

c is a temporary, disposable variable in both cases. (Keep in mind that in Python, all variables are merely references, bound to the objects they represent and capable of being rebound to different objects. Python is more consistent than certain other languages in this respect.)

In your list example, each iteration rebinds c from one integer to another one, leaving the original list unchanged.

In your dict example, each iteration accesses the dict to which c is temporarily bound, rebinding one of that dict's members to a different integer.

In both cases, c is ignored at the end of the loop, but since you've changed a data structure other than c in the second case, you notice the changes when the loop is done.

Forest
+5  A: 

It's not actually acting differently. Changing a variable is not the same as changing the attribute of a variable. You'll see the same thing in the following example:

a = 1
b = a
b = 2 

Here a is still 1. b was assigned a different value and is no longer the same as a

a = {"hello": 1}
b = a
b["hello"] = 2 

Here a["hello] returns 2 instead of 1. b is still the same value because we didn't assign anything to b, and thus b is the same as a. We changed the property ["hello"] of b to 2 and since a and b are the same variable a["hello"] is also 2

Davy8
+15  A: 

It helps to picture what happens to the reference held by c in each iteration:

[ 0, 1, 2, 3, 4, 5 ]
  ^
  |
  c

c holds a reference pointing to the first element in the list. When you do c += 2 (i.e., c = c + 2, the temporary variable c is reassigned a new value. This new value is 2, and c is rebound to this new value. The original list is left alone.

[ 0, 1, 2, 3, 4, 5 ]

  c -> 2

Now, in the dictionary case, here's what c is bound to during the first iteration:

[ {'cow':0}, {'cow':1}, {'cow':2}, {'cow':3}, {'cow':4}, {'cow':5} ]
     ^
     |
     c

Here, c points to the dictionary object {'cow':0}. When you do c['cow'] += 2 (i.e., c['cow'] = c['cow'] + 2), the dictionary object itself is changed, as c is not rebound to an unrelated object. That is, c still points to that first dictionary object.

[ {'cow':2}, {'cow':1}, {'cow':2}, {'cow':3}, {'cow':4}, {'cow':5} ]
     ^
     |
     c
Santa
+1, very very good answer! :)
jathanism
+1 for the pictoral representation
Davy8
A: 

In the second example, you have a list of dictionary objects. c references the dictionary object which is modified inside the loop scope.

Cesar Alaniz
+2  A: 

It's nothing to do with for ... in .... Change your code from for c in cows: to c = cows[3] (and dedent the next line) in each example and see the effect.

In your first example, the list elements are int objects; they are immutable. In the second example, they are dict objects, which are mutable.

John Machin
I accepted your answer because 'immutable' is the magic word. I recall now that ints are separate objects in Python, so it was rebinding that name to another object in the first example, and modifying the same object in the second.
Alex JL
+1  A: 

Regardless of looping, you have to note that:

some_var = some_object

binds the name some_var to the object some_object. The previous object (if any) referenced by some_var is unbound.

some_var[some_index] = some_object

does not bind/unbind some_var; it is just syntactic sugar for the following:

some_var.__setitem__(some_index, some_object)

Obviously, some_var still points to the same indexable (a sequence or a mapping) object as before, which has just been modified.

ΤΖΩΤΖΙΟΥ