Python list generation pitfall [duplicate] - python

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 1 year ago.
Attempt 1
>>> width = 3
>>> height = 2
>>> zeros = [[0]*width]*height
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
[[0, 0, 'foo'], [0, 0, 'foo']]
Expected result was [[0, 0, 0], [0, 0, 'foo']]. It seems that the * operator populated each row with references to the same object, so modifying zeros[1] also modified zeros[0].
Attempt 2
So now we create the initial list object using list comprehension instead:
>>> zeros = [[0 for i in range(width)] for j in range(height)]
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
[[0, 0, 0], [0, 0, 'foo']]
Great, this is the expected result.
The question is...
If the * operator populates each row with references to the same object, then why doesn't every element within each row refer to the same object?
That is, with a (partial) understanding of why Attempt 1 didn't produce the expected output, I would expect that if ANY element of zeros is modified, ALL elements would be changed, that is:
>>> zeros = [[0]*width]*height
>>> print(zeros)
[[0, 0, 0], [0, 0, 0]]
>>> zeros[1][2] = "foo"
>>> print(zeros)
is expected to return:
>>> print(zeros)
[['foo', 'foo', 'foo'], ['foo', 'foo', 'foo']]
but instead returns:
>>> print(zeros)
[[0, 0, 'foo'], [0, 0, 'foo']]
So why does the second * operator populate each row with references to the same object, but each of the elements WITHIN any given row are unique objects?
Answer
List of lists changes reflected across sublists unexpectedly
tl;dr:
Integers are immutable, so [0]*3 creates copies the elements of [0], not references. However [0,0,0] is an object, so [0,0,0]*2 creates a new reference to the original object.

The fundamental disconnect here is that you believe:
zeros[1][2] = "foo"
Is *mutating the element in the rows. It isn't. It is mutating the list at zeros[1], which happens to be the same list at zeros[0].
You can't mutate int objects. Although, they are, in fact, the same int objects:
>>> zeros = [[0]*2]*2
>>> zeros
[[0, 0], [0, 0]]
>>> [[id(x) for x in zs] for zs in zeros]
[[4413403792, 4413403792], [4413403792, 4413403792]]
So, using int objects might actually obscure this, since you might have heard that small integers are cached in CPython. They are always the same object. But consider a user-defined class (which is mutable):
>>> from dataclasses import dataclass
>>> #dataclass
... class Foo:
... foo: int
...
>>> fs = [[Foo(0)]*2]*3
>>> fs
[[Foo(foo=0), Foo(foo=0)], [Foo(foo=0), Foo(foo=0)], [Foo(foo=0), Foo(foo=0)]]
>>> fs[0][0].foo = 8
>>> fs
[[Foo(foo=8), Foo(foo=8)], [Foo(foo=8), Foo(foo=8)], [Foo(foo=8), Foo(foo=8)]]
So note, with the zeros example, there is no method we can call:
zeros[0][0].some_method()
Such that the int object at zeros[0][0] would be mutated, as would happen in the equivalent case of fs[0][0].foo = 8, because int objects don't expose mutator methods. That is what immutable means by definition.

Related

nested append operation goes wrong?

I have two lists: a = [0], b = [[0,1]], I want to append 2 to a first, then append a to b. So b should be [[0,1], [0,2]].
Operations like this work well:
a.append(2)
b.append(a)
but when I tried to combine them:
b.append(a.append(2))
I got the results:
a = [0, 2], b = [[0, 1], None]
What's wrong here?
As answered in the comments, a.append(2) only appends 2 to the list a, but it doesn't actually return it. An append operation that doesn't modify the original list but returns a new list with the appended value can be written simply with the +-operator.
>>> a, b = [0], [[0, 1]]
>>> b.append(a + [2]) # The list that is returned to the append function is [0, 2]
>>> a
[0]
>>> b
[[0, 1], [0, 2]]
I think the comments already answered your question: the append() method modifies the list in place instead of creating a new one, and its return type is None. If you still want to do the operation in a single line, you could use an assignment expression:
a, b = [0], [[0, 1]]
b.append(a := a + [2])
print(a, b)
# [0, 2] [[0, 1], [0, 2]]
If you want to combine those you might make your own function,
def append_ret(x, value):
x.append(value)
return x
doing the operation and returning the 'appended' container.
append_ret(b, append_ret(a,1))

Replace some None in list without affecting other None [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 4 years ago.
I just met something really strange of Python:
>>> out=[[0]*3]*3
>>> out
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> out[0][1]
0
>>> out[0][1]=9
>>> out
[[0, 9, 0], [0, 9, 0], [0, 9, 0]]
well, obviously, what I want is :
[[0, 9, 0], [0, 0, 0], [0, 0, 0]]
isn't strange? I'm not very familiar with Python, but Python always impresses me with its intuitive behavior. But how it comes up with this?
... and how can I get what I need?
thanks!
Watt
A strange behaviour indeed, but that's only because * operator makes shallow copies, in your case - shallow copies of [0, 0, 0] list. You can use the id() function to make sure that these internal lists are actually the same:
out=[[0]*3]*3
id(out[0])
>>> 140503648365240
id(out[1])
>>> 140503648365240
id(out[2])
>>> 140503648365240
Comprehensions can be used to create different lists as follows:
out = [ [0]*3 for _ in range(3) ]
Using * to duplicate elements in lists is a shallow copy operation, so you will end up with multiple references to the same mutable objects if you use this on a list that contains mutable objects.
Instead, use the following to initialize your nested list:
out = [[0]*3 for _ in range(3)]
You can see that with your method, each entry in out is actually a reference to the same list, which is why you see the behavior that you do:
>>> out = [[0]*3]*3
>>> out[0] is out[1] is out[2]
True

Initializing a two-dimensional array in Python? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 6 years ago.
I wonder What is the difference between this two different method of 2D matrix initialization in python.
m =[]
K = []
for x in range(0,W+1):
m.append(0)
for x in range(0,n+1):
K.append(m)
and
l = []
for j in range(0,n+1):
l.append([])
for i in range(0,W+1):
l[j].append(0))
when I tried to print l and K the both gave the same answer but when I tried to implement it in code the output of program changed.
the earlier one(K) gave an incorrect answer but later one(l) when implemented in the program gave a correct answer.
In the first one, you are storing the same reference to list m in k. Change in m will be reflected in all the reference.
Where as in second, you are creating new lists in l. Hence, change in one is independent of another.
Alternatively, simpler way to initialize list is as:
Holding same reference to list (similar to your first approach) is as:
>>> row, columns = 2, 3
>>> my_list = [[0]*columns]*row
>>> my_list
[[0, 0, 0], [0, 0, 0]]
>>> my_list[0][1] = 1 # changing value
>>> my_list
[[0, 1, 0], [0, 1, 0]]
# ^ ^ Reflected in all
Holding different reference to the list (similar to your second approach) is as:
>>> my_list = [[0]*columns for i in range(row)]
>>> my_list
[[0, 0, 0], [0, 0, 0]]
>>> my_list[0][1] = 1 # changing value
>>> my_list
[[0, 1, 0], [0, 0, 0]]
# ^ only one value is changed

How to loop each array for Counter function inside a list using Python?

I want to use counter function to count for the occurrences of the value 0 for each array inside a list.
from collections import Counter
[Counter(x) for x in a]
[Counter(x)[0] for x in a]
Using the above code it only applies to example like:
a = [array([-2, 0, 0]), array([-2, -1, 1])]
When it applies to the code below which have multiple arrays it raises a TypeError:
a = [[array([-2, 0, 0]), array([-2, -1, 1])], [array([3, -1]), array([1, -2])]]
Expected output:
[[2, 0], [0, 0]]
Can anyone help me?
Counter cannot magically descend your list and only count the elements in each array independently. It will always act upon the direct iterable you pass to it. In your first example, you iterate your list and then use each element to create a new Counter; since you have a flat list of array objects, you keep passing array objects to the call. In your second example however, you have a list of lists (which then contain array objects). So when you do the same thing as before, you try to create a counter from those lists. So what the counter tries to do is count how often a certain array object appears in that list. But as array objects are not hashable, it cannot identify them and you get that error. But that’s not the logic you want to use anyway.
Instead, you want to walk through all your lists and whenever you encounter an array, create a counter from it:
def convert (obj):
if isinstance(obj, list):
return list(map(convert, obj))
else:
return Counter(obj)[0]
>>> a = [[array([-2, 0, 0]), array([-2, -1, 1])], [array([3, -1]), array([1, -2])]]
>>> convert(a)
[[2, 0], [0, 0]]
>>> b = [array([-2, 0, 0]), array([-2, -1, 1])]
>>> convert(b)
[2, 0]

difference between list and array(np)? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 8 years ago.
I was trying add ones to some specific position in a 100*100 zero matrix. My code was something this:
adjacency=[[0]*100]*100
while ...
x=...
y=...
adjacency[x][y]=adjacency[x][y]+1
But it adds to other positions as well. In face, adjacency[0]=adjacency[1]=..=adjacency[99], while there's no way that the calculation of x, y in while-loop will produce this kind of result.
So, I changed to array. The only thing I changed was:
adjacency=np.zeros((100,100))
And the result is right this time. Adjacency[i] no longer equals to each other.
Does anyone know why? (Using Python2.7.)
The * operator is not always a good thing for creating lists, specially multi-dimensional lists. This operator just creates copies of the elements in the list to populate the larger list. This works fine for primitive types, but not for other objects.
For example, when you write [0] * 100, it creates a list of 100 elements and assigns 0 to each item. Now you have the reference of a 100-element list. So when you write [[0] * 100] * 100], you get a list of 100 references of the list [0] * 100. So when you change a value, it affects all the 'rows', because all the rows are referencing the same list.
Here's a smaller example:
>>> a = [0] * 3
>>> print a
[0, 0, 0]
>>> b = [a] * 2
>>> print b
[[0, 0, 0], [0, 0, 0]]
>>> a[0] = 1
>>> print a
[1, 0, 0]
>>> print b
[[1, 0, 0], [1, 0, 0]]
>>> b[0][1] = 2
>>> print b
[[1, 2, 0], [1, 2, 0]]
The first row list is copied every time to the 99 other positions. You need to create a new list for each row, something like this:
adjacency=[[0] * 100 for y in range(100)]

Categories

Resources