patternpythonCritical
List of lists changes reflected across sublists unexpectedly
Viewed 0 times
unexpectedlylistreflectedlistsacrosschangessublists
Problem
I created a list of lists:
Then, I changed one of the innermost values:
I expected this to only affect the first sublist, not all of them. That is:
Why did every first element of every sublist change to
See also:
-
How do I clone a list so that it doesn't change unexpectedly after assignment? and
How to initialize a two-dimensional array (list of lists, if not using NumPy) in Python? for workarounds for the problem
-
List of dictionary stores only last appended value in every iteration for an analogous problem with a list of dicts
-
How do I initialize a dictionary of empty lists in Python? for an analogous problem with a dict of lists
>>> xs = [[1] * 4] * 3
>>> print(xs)
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]Then, I changed one of the innermost values:
>>> xs[0][0] = 5
>>> print(xs)
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]I expected this to only affect the first sublist, not all of them. That is:
>>> print(xs)
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]Why did every first element of every sublist change to
5?See also:
-
How do I clone a list so that it doesn't change unexpectedly after assignment? and
How to initialize a two-dimensional array (list of lists, if not using NumPy) in Python? for workarounds for the problem
-
List of dictionary stores only last appended value in every iteration for an analogous problem with a list of dicts
-
How do I initialize a dictionary of empty lists in Python? for an analogous problem with a dict of lists
Solution
When you write
To fix it, you need to make sure that you create a new list at each position. One way to do it is
which will reevaluate
You might wonder why
The only option
In contrast, a list comprehension reevaluates the element expression on every iteration.
Incidentally,
[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 [x2 for x in range(3)] reevaluates x2 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.Code Snippets
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]][[1]*4 for _ in range(3)]Context
Stack Overflow Q#240178, score: 819
Revisions (0)
No revisions yet.