Confusion about variable in python. How python use variables? [duplicate] - python

This question already has answers here:
What is the difference between shallow copy, deepcopy and normal assignment operation?
(12 answers)
Closed 3 years ago.
When I run this,
key=[[0, 0, 0], [1, 0, 0], [0, 1, 1]]
key_=[[0, 0, 0], [1, 0, 0], [0, 1, 1]]
m=3
for i_ in range(1,m+1):
for j_ in range(1,m+1):
key[j_-1][m+1-i_-1]=key_[i_-1][j_-1]
print(key,key_,sep='\n')
I got this:
>>> [[0, 1, 0], [1, 0, 0], [1, 0, 0]]
[[0, 0, 0], [1, 0, 0], [0, 1, 1]]
However, when I changed only the second line,
key=[[0, 0, 0], [1, 0, 0], [0, 1, 1]]
key_=key
m=3
for i_ in range(1,m+1):
for j_ in range(1,m+1):
key[j_-1][m+1-i_-1]=key_[i_-1][j_-1]
print(key,key_,sep='\n')
I got this:
>>> [[0, 1, 0], [0, 0, 0], [0, 0, 0]]
[[0, 1, 0], [0, 0, 0], [0, 0, 0]]
Why do the two codes have different results?
In my opinion, they should be same.
Why the different parts make different results?

In Python, variables which contain lists are more like pointers. When you say key_=key, you're telling Python to use the same list in both cases, not a copy. If you want a copy of the original list, use key_=[x.copy() for x in key]. This will copy the contents of each item (which are lists) in key.
Examples:
Make two lists which contain 1, 2 and 3
>>> my_list = [1,2,3]
>>> my_list
[1, 2, 3]
>>> my_list2 = my_list
>>> my_list2
[1, 2, 3]
Now let's edit the value of the original list...
>>> my_list[0] = 10
>>> my_list
[10, 2, 3]
>>> my_list2
[10, 2, 3]
The changes are copied because my_list and my_list2 are the same, not just a copy of it.
Let's change my_list2:
>>> my_list2[1] = 20
>>> my_list2
[10, 20, 3]
>>> my_list
[10, 20, 3]
And once again, the values are updated between the two as they both point to the same list.
Now let's see what happens if we use the copy method:
>>> my_list_copy = my_list.copy()
>>> my_list_copy
[10, 20, 3]
>>> my_list[0] = 1
>>> my_list
[1, 20, 3]
>>> my_list_copy
[10, 20, 3]
And so we can see that the two lists start with the same contents, but are different.
Using the is operator, we can also see this difference between the 3 list variables:
>>> my_list is my_list2
True
>>> my_list is my_list_copy
False
>>>
EDIT:
key is a list containing lists as items. When the copy method is called, only the outer list is copied, so the actual items in both lists (ie the sub-lists) are identical. To copy these by value rather than effectively by reference, we can use a simple list comprehension:
key_=[x.copy() for x in key]
This code copies each item in key by value, and creates a new list with these as items.
The output using this is
[[0, 1, 0], [1, 0, 0], [1, 0, 0]]
[[0, 0, 0], [1, 0, 0], [0, 1, 1]]

Related

How to iterate over a list that has duplicate values?

This is probably a very basic question but I dont know what I have to search for to find the answer for it:
I have this code:
list = [[0,1],[0,2],[1,3],[1,4],[1,5]]
list.append(list[0])
for i in list:
i.append(0)
print(list)
This List will later be used as coordinates for a curve. I need to duplicate the first coordinate at the end to get a closed curve.
If I then want to add a third value to each coordinate in the list the first and last item in list will be iterated over twice:
[[0, 1, 0, 0], [0, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [0, 1, 0, 0]]
I am guessing they have the same memory address and thereby the append-function is applied to the same object at this address once for the first index and once for the last.
What is this phenomenon called ? what is the easiest way to get the list like this:
[[0, 1, 0], [0, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [0, 1, 0]]
Thank you for your help
You can do a list comprehension:
list = [[0,1],[0,2],[1,3],[1,4],[1,5]]
list.append(list[0])
list = [x + [0] for x in list]
print(list)
# [[0, 1, 0], [0, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [0, 1, 0]]
EDIT: The trick here is, using x + [0] within the list comprehension. This way new lists are created, thus you do not append 0 to the same list twice (Hattip to #dx_over_dt)
The problem you have with your approach is, that the first and last element of your list refers to the very same object. You can see this, when you print i and list for every iteration:
for i in list:
i.append(0)
print(i)
print(list)
So for the first and last i in your loop, you will append a 0 to the very same list.
You could stick to your approach appending a copy of the first element:
list.append(list[0].copy())
The simplest answer is to add the 0's before appending the closing point.
list = [[0,1],[0,2],[1,3],[1,4],[1,5]]
for i in list:
i.append(0)
list.append(list[0])
print(list)
It's the tiniest bit more efficient than a list comprehension because it's not making copies of the elements.

Python - Create constant array of unique elements [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 5 years ago.
I recently tried to instantiate a 4x4 constant (0's) array by using
a = [[0] * 4] * 4
which instantiates the array a as
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
However, this does not create an array of unique elements as altering an element in any of the arrays changes all of them, e.g.:
a[0][0] = 1
alters a to
[[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0]]
I think I understand why this happens (copies of lists copy the list's pointer and do not create separate copies unless specified, unlike int's, etc.) but am left wondering:
Is there any quick and easy way to instantiate a constant array (without using any external modules, such as NumPy) with unique elements that can later be altered by simple a[i][j] = x addressing?
a = [[0 for _ in xrange(4)] for _ in xrange(4)]
should do it, it'll create separate lists
Just for free. What is going on here ? When one does
>>> a = [[0] * 4] * 4
first, one creates one list [0] * 4 with four 0 elements. Let call this list li.
Then when you do [li] * 4, one actually creates a list which refers four times to the same object. See
>>> [id(el) for el in a]
[8696976, 8696976, 8696976, 8696976] # in my case
Whence the (not that) curious result one gets when entry-wise assigning like so
>>> a[0][0] = 1
[[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0]]
A solution is simply to ensure that each element of the list really is unique. For example doing
#Python2
>>> a = map(lambda _: [0]*4, range(4))
#Python3
>>> a = list(map(lambda _: [0]*4, range(4)))
#Python2&3
>>> a[0][0] = 1
[[1, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]

python how to change element in nested list [duplicate]

This question already has answers here:
why can't I change only a single element in a nested list in Python [duplicate]
(2 answers)
Closed 8 years ago.
I have been using python for a lot and today I get surprised by a simple nested list.
How can I change the value of an element of my list?
>>> l=[[0,0]]*10
>>> l
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
>>> l[0]
[0, 0]
>>> l[0][0]
0
>>> l[0][0]=1 #here comes the question
>>> l
[[1, 0], [1, 0], [1, 0], [1, 0], [1, 0], [1, 0], [1, 0], [1, 0], [1, 0], [1, 0]]
>>>
I would expect as final result
l=[[1,0],[0,0],[0,0]...]
Why it does not append? (I would like a theoretical explanation )
How can I get the desired result?
Thanks.
EDIT:
In starting the list in another way I get the desired result
>> l=[[0,0],[0,0]]
>>> l
[[0, 0], [0, 0]]
>>> l[0][0]=1
>>> l
[[1, 0], [0, 0]]
>>>
but I am still wondering why the first version does not work and how can I initialize a list of a lot of elements?
You have a list of the same [0, 0] element 10 times here:
l=[[0,0]]*10
Any time you modify one, it modifies them all, because they're the same list.
One safe way to make them unique would be:
l = [[0, 0] for _ in range(10)]
One easy way to check would be to print the id of each, which is the memory address where it's stored:
>>> for element in l:
... print id(element)
...
34669128
34669128
34669128
34669128
34669128
34669128
34669128
34669128
34669128
34669128

Changing a value in one list changes the values in another list with a different memory ID

I have been beating my head against a wall over this problem. I create a list and make 4 copies, only one of which shares the same memory index. If I change the original list, is somehow changes 3 of those copies as well, 2 of which have a different memory index. Only if I make a list using the same command as the original, am I able to create a list that is not impacted by changes to the original. How is this possible? Here is the output from my console:
>>> orig=[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]]
>>> id(orig)
151498220
>>> copy1=orig #same index
>>> id(copy1)
151498220
>>> copy2=orig[:] #different index
>>> id(copy2)
151498348
>>> copy3=list(orig) #different index
>>> id(copy3)
151503020
>>> copy4=[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]]
>>> id(copy4)
151498636
>>> orig[0][1]=34
>>> copy1
[[0, 34, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]] #expected, same memory index
>>> copy2
[[0, 34, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]] #WTF?!?!?
>>> copy3
[[0, 34, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]] #ARGH!!!
>>> copy4
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]] #workaround?
>>> id(orig)
151498220
>>> id(copy1)
151498220
>>> id(copy2)
151498348
>>> id(copy3)
151503020
>>> id(copy4)
151498636
The memory indices did not change and yet the lists were altered. Only copy1 should have changed as it has the same memory index as orig.
That's because you are just creating a shallow copy. You need to create a deep copy instead.
As per copy module doc:
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in
the original.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
You can verify it by comparing the id of inner list:
>>> orig=[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]]
>>> id(orig)
151498220
>>> copy2=orig[:] #different index
>>> id(copy2)
151498348
>>> id(copy2[0]) == id(orig[0]) # inner list have same id
True
You can create a deepcopy using copy.deepcopy(x):
>>> import copy
>>>
>>> copy3 = copy.deepcopy(orig)
>>>
>>> id(copy3[0]) == id(orig[0]) # inner list have different id
False
>>> orig[0][3] = 34
>>>
>>> orig
[[0, 34, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]]
>>> copy3
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]]
This is because while the outer lists (copy1, copy2, etc.) are separate objects, their sublists are all references to the same lists you created in orig. Try doing:
id(orig[0]) == id(copy3[0]) #should return True
In order to make a deep copy of the list object and all the objects it references, use deepcopy.
Your list is a list of names, not a list of lists as you are thinking of it. When you make a copy of the list using any of the methods you list (slicing, creating a new list based on the old one, etc) you make a new outer list, but the names in the new list reference the same internal lists as the names in the old one.
# One through three are all examples of:
first_list, second_list, third_list = [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 123, 0]
original = [first_list, second_list, third_list]
another_list = original[:]
# We do indeed have another list
assert id(original) != id(another_list)
# But the *references* in the list are pointing at the same underlying child list
assert id(original[0]) == id(another_list[0])

Python list bug or am I wrong?

I have constructed a 3 level nested list
#run on Python 3.2.3 32 bit on Win 7
L2=list(0 for i in range(2))
L3=list(L2 for i in range(3))
L4=list(L3 for i in range(4))
#give a new value to the very first number in list:
L4[0][0][0]=5
print("L4:")
print(L4)
#outputs erronously:
#[[[5, 0], [5, 0], [5, 0]], [[5, 0], [5, 0], [5, 0]], [[5, 0], [5, 0], [5, 0]], [[5, 0], [5, 0], [5, 0]]]
The same list given explicitly
#the same L4 given explicitly:
anotherL4=[[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]
print("anotherL4:")
#give a new value to the very first number:
anotherL4[0][0][0]=5
print(anotherL4)
#outputs correctly:
#[[[5, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]
You're wrong. You've copied the reference multiple times, which means they're actually all the same list.
When you write list(L3 for i in range(4)), you are telling it to yield the same list L3 on each iteration of the generator comprehension. When you subsequently modify that list, the modifications show up everywhere, because all of them are references to the same list.
You could get the effect you seem to want by doing
list(list(L3) for i in range(4))
since using list(L3) makes a new list.
Just to elaborate,
a = 1
b = 2
c = [a,b]
a += 1
print c
Your problem is that you built a list of list references rather than a list of lists. Since the references all pointed back to a single list, when you mutate that single list, all the references show the change.
L0 = range(3)
L1 = range(3)
print(id(L0)) # prints a number
print(id(L1)) # prints a different number
print(id(L0) == id(L1)) # prints False
print(L0 is L1) # prints False; not same objects
print(L0 == L1) # prints True; values match
# your original code:
L2=list(0 for i in range(2))
L3=list(L2 for i in range(3))
L4=list(L3 for i in range(4))
print(L3[0] is L2) # prints True; L3[0] is a reference to L2
We can fix it and explicitly show what we are doing by using copy.deepcopy():
import copy
L2 = [0 for i in range(2)]
L3 = [copy.deepcopy(L2) for i in range(3)]
L4 = [copy.deepcopy(L3) for i in range(4)]
#give a new value to the very first number in list:
L4[0][0][0]=5
print("L4:")
print(L4)
Note that instead of making a generator expression and passing it to list() to force it to be expanded out to a list, I just used list comprehensions in the above code to directly make lists.
More usually if you want to do this crazy thing, you should maybe just nest some list comprehensions:
L4 = [[[0 for _ in range(2)] for _ in range(3)] for _ in range(4)]
This makes it pretty clear that we are building a new list of lists of lists. And if you use copy.deepcopy() you are basically just copying a bunch of zeroes, so you might as well just build new lists using zeroes.

Categories