Вопрос-Ответ

List of lists changes reflected across sublists unexpectedly

Список изменений списков, неожиданно отраженных в подсписках

Я создал список списков:

>>> xs = [[1] * 4] * 3
>>> print(xs)
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

Затем я изменил одно из самых внутренних значений:

>>> xs[0][0] = 5
>>> print(xs)
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

Почему каждый первый элемент каждого подсписка изменился на 5?



Смотрите также:



Переведено автоматически
Ответ 1

Когда вы пишете, [x]*3 вы получаете, по сути, список [x, x, x]. То есть список с 3 ссылками на одно и то же x. Когда вы затем изменяете этот сингл, x он виден по всем трем ссылкам на него:

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)]

который будет пересматриваться [1]*4 каждый раз вместо того, чтобы оценивать его один раз и делать 3 ссылки на 1 список.


Вы можете задаться вопросом, почему * нельзя создавать независимые объекты так, как это делает понимание списка. Это потому, что оператор умножения * работает с объектами, не видя выражений. Когда вы используете * для умножения [[1] * 4] на 3, * видит только список из 1 элемента, который [[1] * 4] вычисляется, а не [[1] * 4 текст выражения. * понятия не имеет, как сделать копии этого элемента, не имеет, как провести переоценку [[1] * 4], и понятия не имеет, что вам даже нужны копии, и, в общем, может даже не быть способа скопировать элемент.

Единственный вариант, который * есть, - это создавать новые ссылки на существующий подсписк вместо попыток создания новых подсписков. Все остальное было бы непоследовательным или потребовало бы серьезной переработки фундаментальных решений при проектировании языка.

Напротив, понимание списка повторно оценивает выражение элемента на каждой итерации. [[1] * 4 for n in range(3)] пересматривает [1] * 4 каждый раз по одной и той же причине, [x**2 for x in range(3)] пересматривает x**2 каждый раз. Каждая оценка [1] * 4 генерирует новый список, поэтому понимание списка делает то, что вы хотели.

Кстати, [1] * 4 также не копирует элементы [1], но это не имеет значения, поскольку целые числа неизменяемы. Вы не можете сделать что-то вроде 1.value = 2 и превратить 1 в 2.

Ответ 2
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for _ in range(size)]

Визуализация в реальном времени с помощью Python Tutorial:

Фреймы и объекты

Ответ 3

На самом деле, это именно то, чего вы ожидали. Давайте разберем, что здесь происходит:

Вы пишете

lst = [[1] * 4] * 3

Это эквивалентно:

lst1 = [1]*4
lst = [lst1]*3

Это означает, что lst представляет собой список, на который указывают 3 элемента lst1. Это означает, что две следующие строки эквивалентны:

lst[0][0] = 5
lst1[0] = 5

As lst[0] - это не что иное, как lst1.

Для получения желаемого поведения вы можете использовать понимание списка:

lst = [ [1]*4 for n in range(3) ]

В этом случае выражение повторно вычисляется для каждого из них n, что приводит к созданию другого списка.

Ответ 4
[[1] * 4] * 3

или даже:

[[1, 1, 1, 1]] * 3

Создает список, который ссылается на внутренний [1,1,1,1] 3 раза, а не на три копии внутреннего списка, поэтому каждый раз, когда вы изменяете список (в любой позиции), вы увидите изменение три раза.

Это то же самое, что и в этом примере:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

где это, вероятно, немного менее удивительно.

python list