Italo Lopes
Italo Lopes

Reputation: 83

Python - For inside for loop not working properly

I create a matrix with 4 lines and 5 columns filled with zeros. Then i put some values into the first line (line 0) and want to calculate values for the second line (line 1) with a function. My code is:

x = [0,1,2,1,0]
u=[[0]*(len(x))]*(4)
u[0]=x
for n in range(0,1):
    print u
    for j in range(1,4):
        u[n+1][j]=u[n][j]-(u[n][j+1]-u[n][j-1])
        print 'Value %.f at position %.f %.f' %(u[n+1][j],n+1,j)
    print u

But the results of prints is:

[[0, 1, 2, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
Value -1 at position 1 1
Value 2 at position 1 2
Value 3 at position 1 3
[[0, 1, 2, 1, 0], [0, -1, 2, 3, 0], [0, -1, 2, 3, 0], [0, -1, 2, 3, 0]]

I don't understand why the program calculates values for the line 1 and columns 1, 2 and 3 but after the loop lines 2 and 3 are also with values. I expect the results of prints was:

[[0, 1, 2, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
Value -1 at position 1 1
Value 2 at position 1 2
Value 3 at position 1 3
[[0, 1, 2, 1, 0], [0, -1, 2, 3, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

Upvotes: 0

Views: 1101

Answers (1)

Toni
Toni

Reputation: 61

This question has been answered before multiple times, such as here and here

The short story is that when you multiply [0]*(len(x)) you get a list with 5 elements referencing the number 0. Let's call this list "Joe". The rest of your expression creates another list with a reference to "Joe" in it, which is multiplied 4 times, resulting in a list with 4 references to the same "Joe"(list). You think you have 20 values when in fact you only have 10: 1 the value for the number 0, 5 values in a list, all holding the reference to the number 0, 4 values in the outermost list, all holding the reference to the list with 5 values. (1+5+4=10, maybe this would make a better sense as a diagram)

I'm going to attempt to break it down further, specifically for your code:

(We'll try to clean up your expression a bit from u=[[0]*(len(x))]*(4) to u=[[0]*5]*4 ) This can then be expanded into:

element=[0]
line=[element*5]
u=line*4

The key point is that when we print line we will get

[[0, 0, 0, 0, 0]]

and this means

u=line*4 

is the same as

u=[[0, 0, 0, 0, 0]]*4`

so when you print u you get the expected

[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

Now, when you modify one of the "cells", say the third one on the third line

u[2][2]=1

You get

[[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]]

You may say that's odd and inconsistent: both element and line are lists, how come values are only copied "vertically"?

This oddity is due to fact that in Python containers (such as lists) only use references:

Every object has an identity, a type and a value. An object’s identity never changes once it has been created; you may think of it as the object’s address in memory.

Some objects contain references to other objects; these are called containers. Examples of containers are tuples, lists and dictionaries. The references are part of a container’s value. (but the actual values of the referenced items are not)

(see more in Python's data model)

....and you can think of the * operator as something like this:

def multiply(list, times):
    result = []
    for i in range(times):
        value=list[i % len(list)]
        result.append(value)
    return result

with your code rewritten as

u = multiply([multiply([0],5)],4)

Upvotes: 1

Related Questions