List of Lists Changes Reflected Across Sublists Unexpectedly

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.

Changing an element in one list changes multiple lists

What matters is how you created your original mysolution list. As it seems, it contains four times the same list which is why changing it once will make it change in all four locations.

To initialize independent zero-filled lists like that, you can do the following:

mysolution = [[0] * 4 for i in range(4)]

Assignment of variable to list using indexing

Your code reads:

def func(first):
third = first[0]
first[0][0] = 5
print(third)

first = [[3,4]]
func(first)

What's happening is this:

  • In func(), the argument first contains a reference to a list of lists with value [[3,4]].
  • After the assignment to third, third contains a reference to the list [3,4] at position 0 in the list referenced by first. No new list object has been created and no copy of a list has taken place, rather a new reference to the existing list has been created and stored in the variable third.
  • In the line first[0][0] = 5, the item at position 0 in the list [3,4] is updated so that the list is now [5,4]. Note that the list [3,4] that was modified is an element of the list of lists referenced by first, and it is also the one referenced by third. Because the object (namely, the list) that is referenced by third has now been modified, any use of this reference to access the list (such as print(third)) will reflect its updated value, which is [5,4].

UPDATE:

The code for your updated question is:

def func(first):
third = first[0][0:2]
first[0][0] = 5
print(third)

first = [[3,4]]
func(first)

In this case, the assignment third = first[0][0:2] takes a slice of the list [3,4] at position 0 in the list of lists referenced by first. Taking a slice in this way creates a new object which is a copy of the subsequence indicated by the arguments specified in the square brackets, so after the assignment, the variable third contains a reference to a newly created list with value [3,4]. The subsequent assignment first[0][0] = 5 updates the value of the list [3,4] in position 0 of the list of lists referenced by first, with the result that the value of the list becomes [5,4], and has no effect on the value of third which is an independent object with value [3,4].

Importantly (and potentially confusingly), slice notation used on the left-hand side of an assignment works very differently. For example, first[0][0:2] = [5,4] would change the contents of the list first[0] such that the elements in index 0 and 1 are replaced by [5,4] (which in this case means the value of the list object would be changed from [3,4] to [5,4], but it would be the same object).



Related Topics



Leave a reply



Submit