Python List Multiplication: [[...]]*3 Makes 3 Lists Which Mirror Each Other When Modified

Python list multiplication: [[...]]*3 makes 3 lists which mirror each other when modified

You've made 3 references to the same list.

>>> a = b = []
>>> a.append(42)
>>> b
[42]

You want to do this:

P = [[()] * 3 for x in range(3)]

Generating sublists using multiplication ( * ) unexpected behavior

My best guess is that using multiplication in the form [[]] * x causes Python to store a reference to a single cell...?

Yes. And you can test this yourself

>>> lst = [[]] * 3
>>> print [id(x) for x in lst]
[11124864, 11124864, 11124864]

This shows that all three references refer to the same object. And note that it really makes perfect sense that this happens1. It just copies the values, and in this case, the values are references. And that's why you see the same reference repeated three times.

It is interesting to note that if I do

lst = [[]]*3
lst[0] = [5]
lst[0].append(3)

then the 'linkage' of cell 0 is broken and I get [[5,3],[],[]], but lst[1].append(0) still causes [[5,3],[0],[0].

You changed the reference that occupies lst[0]; that is, you assigned a new value to lst[0]. But you didn't change the value of the other elements, they still refer to the same object that they referred to. And lst[1] and lst[2] still refer to exactly the same instance, so of course appending an item to lst[1] causes lst[2] to also see that change.

This is a classic mistake people make with pointers and references. Here's the simple analogy. You have a piece of paper. On it, you write the address of someone's house. You now take that piece of paper, and photocopy it twice so you end up with three pieces of paper with the same address written on them. Now, take the first piece of paper, scribble out the address written on it, and write a new address to someone else's house. Did the address written on the other two pieces of paper change? No. That's exactly what your code did, though. That's why the other two items don't change. Further, imagine that the owner of the house with address that is still on the second piece of paper builds an add-on garage to their house. Now I ask you, does the house whose address is on the third piece of paper have an add-on garage? Yes, it does, because it's exactly the same house as the one whose address is written on the second piece of paper. This explains everything about your second code example.

1: You didn't expect Python to invoke a "copy constructor" did you? Puke.

List of lists changes reflected across sublists unexpectedly

When you write [x]*3 you get, essentially, the list [x, x, x]. That is, a list with 3 references to the same x. When you then modify this single x it is visible via all three references to it:

x = [1] * 4
xs = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
f"id(xs[0]): {id(xs[0])}\n"
f"id(xs[1]): {id(xs[1])}\n"
f"id(xs[2]): {id(xs[2])}"
)
# id(xs[0]): 140560897920048
# id(xs[1]): 140560897920048
# id(xs[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"xs: {xs}")
# xs: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

To fix it, you need to make sure that you create a new list at each position. One way to do it is

[[1]*4 for _ in range(3)]

which will reevaluate [1]*4 each time instead of evaluating it once and making 3 references to 1 list.


You might wonder why * can't make independent objects the way the list comprehension does. That's because the multiplication operator * operates on objects, without seeing expressions. When you use * to multiply [[1] * 4] by 3, * only sees the 1-element list [[1] * 4] evaluates to, not the [[1] * 4 expression text. * has no idea how to make copies of that element, no idea how to reevaluate [[1] * 4], and no idea you even want copies, and in general, there might not even be a way to copy the element.

The only option * has is to make new references to the existing sublist instead of trying to make new sublists. Anything else would be inconsistent or require major redesigning of fundamental language design decisions.

In contrast, a list comprehension reevaluates the element expression on every iteration. [[1] * 4 for n in range(3)] reevaluates [1] * 4 every time for the same reason [x**2 for x in range(3)] reevaluates x**2 every time. Every evaluation of [1] * 4 generates a new list, so the list comprehension does what you wanted.

Incidentally, [1] * 4 also doesn't copy the elements of [1], but that doesn't matter, since integers are immutable. You can't do something like 1.value = 2 and turn a 1 into a 2.

Multidimensional python arrays

This is a fairly common question;

[0] * 3

results in a list containing three 0s, but

[[]] * 3

results in a list containing three references to a single actual list.

You need to do something like

A = [[[0] * 3] for j in range(3)] for k in range(3)]

to actually create what you want.

python initialize a list of dict likely just copy reference

The difference comes from how the elements of your list are defined. In the first case, you create a list of two dictionaries, but those dictionaries contain the same reference, i.e., they're pointing to the same memory space. When you do a[0]['x'] = 1 You're changing the data the dictionary is pointing to and since both dictionaries in your list are pointing to the same space, they both appear to update.

In the second case, you have a list of two numbers, both of which technically point to the same spot (where python has pre-allocated zero). When you do a[0] = 1, you're not changing the data in the space where the first element is pointing, you're resetting where that element is pointing. Specifically you're redirecting it to 1. That way only the first element updates, but the second is still pointing at 0.

Python linking strings together in list

This creates a list of references to the same list

self.board = [['']*size]*size

You need to make a new list for each entry like this

self.board = [['']*size for x in range(size)]

It's only safe to multiply immutable objects across a list like this. Eg

['']*size
[0]*size

Since you are forced to replace the entries rather than modify them



Related Topics



Leave a reply



Submit