Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python: loop a list of list and assign value inside the loop

Tags:

python

I have the following code, why the first one doesn't change alist while the second changes it?

alist = [[1,2], [3,4], [5,6]]
for item in alist:
    item = 1
print(alist)

alist = [[1,2], [3,4], [5,6]]
for item in alist:
    item = item.append(10)
print(alist)
like image 954
Daniel Wu Avatar asked Oct 09 '16 08:10

Daniel Wu


People also ask

How do you assign a value to a list in Python?

Assignment with an = on lists does not make a copy. Instead, assignment makes the two variables point to the one list in memory. The "empty list" is just an empty pair of brackets [ ]. The '+' works to append two lists, so [1, 2] + [3, 4] yields [1, 2, 3, 4] (this is just like + with strings).


2 Answers

This is a somewhat unintuitive behavor of variables. It happens because, in Python, variables are always references to values.

Boxes and tags

In some languages, we tend to think about variables as "boxes" where we put values; in Python, however, they are references, and behave more like tags or "nicknames" to values. So, when you attribute 1 to item, you are changing only the variable reference, not the list it is pointing to.

A graphic representation can help. The image below represents the list created by alist = [[1,2], [3,4], [5,6]]

A list with three sublists

Given that, let's see what happens when we execute your first loop.

The first loop

When you execute for item in alist, you are asking the interpreter to take each value from the list, one per time, put it on the variable item and do some operation in it. In the first operation, for example, we have this new schema:

Now a variable points to a sublist

Note that we do not copy the sublist to item; instead, we point to it through item. Then, we execute item = 1 — but what does it mean? It mean that we are making item point to the value 1, instead of pointing to the sublist:

item points to other value now

Note that the old reference is lost (it is the red arrow) and now we have a new. But we just changed a variable pointing to a list — we did not alter the list itself.

Then, we enter to the second iteration of the loop, and now item points to the second sublist:

item pointing to the second sublist

When we execute item = 1, again, we just make the variable point to aonther value, without changing the list:

enter image description here

Now, what happens when we execute the second loop?

The second loop

The second loop starts as the first one: we make item refer to the first sublist:

Now a variable points to a sublist

The first difference, however, is that we call item.append(). append() is a method, so it can change the value of the object it is calling. As we use to say, we are sending a message to the object pointed by item to append the value 10. In this case, the operation is not being made in the variable item, but directly in the object it refers! So here is the result of calling item.append():

A list has grown

However, we do not only append a value to the list! We also assign the value returned by item.append(). This will sever the item reference to the sublist, but here is a catch: append() returns None.

enter image description here

The None value

None is a value that represents, basically, the unavailability of a relevant value. When a function returns None, it is saying, most of the time, "I have nothing relevant to give you back." append() does change its list directly, so there is nothing it needs to return.

It is important because you probably believed item would point to the appended list [1, 2, 10], right? No, now it points to None. So, you would expect the code below...

alist = [[1,2], [3,4], [5,6]]
for item in alist:
    item = item.append(10)
    print(item)

To print something like this:

[1, 2, 10]
[3, 4, 10]
[5, 6, 10]

But this does not happen. This is what happens:

>>> alist = [[1,2], [3,4], [5,6]]
>>> for item in alist:
...     item = item.append(10)
...     print(item)
...
None
None
None

Yet, as we commented, the append() method changed the lists themselves. So, while the item variable was useless after the assignment, the final list is modified!

>>> print alist
[[1, 2, 10], [3, 4, 10], [5, 6, 10]]

If you want to use the appended list inside the loop, just do not assign the method returned value to item. Do this:

>>> alist = [[1,2], [3,4], [5,6]]
>>> for item in alist:
...     item.append(10)
...     print item
... 
[1, 2, 10]
[3, 4, 10]
[5, 6, 10]

This works because item will still point to the list.

Conclusion

References are somewhat complex to understand at first, let alone master. Yet, they are really powerful and can be learned if you follow examples etc. Your example is a bit complicated because there is more happening here.

The Python Tutor can help you understand what is going on, because it executes each step graphically. Check your own code running there!

like image 63
brandizzi Avatar answered Sep 22 '22 17:09

brandizzi


In the first example item is bound to each element in list alist, and then item is rebound to the integer 1. This does not change the element of the list - it merely rebinds the name item to the int object 1.

In the second example the list element (itself a list) is mutated by append(). item is still bound to the sub-list so item.append() mutates the sub-list.

like image 33
mhawke Avatar answered Sep 20 '22 17:09

mhawke