Изменения списка списков неожиданно отражаются в подсписках
Мне нужно было создать список списков на Python, поэтому я набрал следующее:
myList = [[1] * 4] * 3
Список выглядел так:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Затем я изменил одно из самых сокровенных значений:
myList[0][0] = 5
Теперь мой список выглядит так:
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
а это не то, чего я хотел или ожидал. Может кто-нибудь объяснить, что происходит, и как это обойти?
Ответы
Когда вы пишете, [x]*3
вы, по сути, получаете список [x, x, x]
. То есть список с 3 ссылками на одно и то же x
. Когда вы затем изменяете этот сингл, x
он виден по всем трем ссылкам на него:
x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
f"id(l[0]): {id(l[0])}\n"
f"id(l[1]): {id(l[1])}\n"
f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048
x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[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.
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
Live Python Tutor Visualize
Собственно, этого и следовало ожидать. Разложим то, что здесь происходит:
Ты пишешь
lst = [[1] * 4] * 3
Это эквивалентно:
lst1 = [1]*4
lst = [lst1]*3
Это означает, lst
что это список из трех элементов, на которые все указывают lst1
. Это означает, что две следующие строки эквивалентны:
lst[0][0] = 5
lst1[0] = 5
Как lst[0]
ничего, кроме lst1
.
Чтобы получить желаемое поведение, вы можете использовать понимание списка:
lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2
В этом случае выражение повторно вычисляется для каждого n, что приводит к другому списку.
[[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-2.x, используйте, xrange()
который возвращает генератор, который более эффективен ( range()
в python 3 выполняет ту же работу) _
вместо переменной throwaway n
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
Кроме того, как гораздо более питонический способ, который вы можете использовать itertools.repeat()для создания объекта-итератора из повторяющихся элементов:
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]
PS Использование NumPy, если вы хотите создать массив из единиц или нулей , которые можно использовать np.ones
и np.zeros
и / или для другого использования номера np.repeat()
:
In [1]: import numpy as np
In [2]:
In [2]: np.ones(4)
Out[2]: array([ 1., 1., 1., 1.])
In [3]: np.ones((4, 2))
Out[3]:
array([[ 1., 1.],
[ 1., 1.],
[ 1., 1.],
[ 1., 1.]])
In [4]: np.zeros((4, 2))
Out[4]:
array([[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.]])
In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])
Контейнеры Python содержат ссылки на другие объекты. См. Этот пример:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
Это b
список, содержащий один элемент, который является ссылкой на список a
. Список a
изменяемый.
Умножение списка на целое число эквивалентно добавлению списка к самому себе несколько раз (см. Общие операции с последовательностью ). Итак, продолжая пример:
>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]
Мы видим, что список c
теперь содержит две ссылки на список, a
который эквивалентен c = b * 2
.
Python FAQ также содержит объяснение этого поведения: Как мне создать многомерный список?
myList = [[1]*4] * 3
создает [1,1,1,1]
в памяти один объект списка и 3 раза копирует его ссылку. Это эквивалентно obj = [1,1,1,1]; myList = [obj]*3
. Любая модификация obj
будет отражена в трех местах, где бы ни obj
была ссылка в списке. Правильное утверждение было бы таким:
myList = [[1]*4 for _ in range(3)]
или же
myList = [[1 for __ in range(4)] for _ in range(3)]
Здесь важно отметить, что *
оператор в основном используется для создания списка литералов . Хотя 1
это неизменяемый, obj =[1]*4
все равно будет создавать список, 1
повторяющийся 4 раза, чтобы сформировать [1,1,1,1]
. Но если делается какая-либо ссылка на неизменяемый объект, объект перезаписывается новым.
Это означает, что если мы это сделаем obj[1]=42
, то obj
станет [1,42,1,1]
не так,
как некоторые думают. Это также можно проверить:
[42,42,42,42]
>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]
>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440
>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]
>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440
Давайте перепишем ваш код следующим образом:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
Затем запустите следующий код, чтобы все стало более понятным. Код в основном печатает ids полученных объектов, которые
Вернуть «идентичность» объекта
и поможет нам их выявить и проанализировать, что происходит:
print("myList:")
for i, subList in enumerate(myList):
print("\t[{}]: {}".format(i, id(subList)))
for j, elem in enumerate(subList):
print("\t\t[{}]: {}".format(j, id(elem)))
И вы получите следующий результат:
x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
[0]: 4300763792
[0]: 4298171528
[1]: 4298171528
[2]: 4298171528
[3]: 4298171528
[1]: 4300763792
[0]: 4298171528
[1]: 4298171528
[2]: 4298171528
[3]: 4298171528
[2]: 4300763792
[0]: 4298171528
[1]: 4298171528
[2]: 4298171528
[3]: 4298171528
Итак, давайте перейдем к шагу за шагом. У вас есть x
который есть 1
, и список из одного элемента, y
содержащий x
. Ваш первый шаг - y * 4
получить новый список z
, [x, x, x, x]
то есть создать новый список, который будет иметь 4 элемента, которые являются ссылками на исходный x
объект. Чистый шаг очень похож. Вы в основном делаете z * 3
, что есть [[x, x, x, x]] * 3
и возвращается [[x, x, x, x], [x, x, x, x], [x, x, x, x]]
по той же причине, что и для первого шага.
Проще говоря, это происходит потому, что в python все работает по ссылке , поэтому, когда вы создаете список списка таким образом, вы в основном сталкиваетесь с такими проблемами.
Чтобы решить вашу проблему, вы можете выполнить любой из них: 1. Используйте документацию по массиву numpy для numpy.empty 2. Добавляйте список по мере того, как вы попадаете в список. 3. Вы также можете использовать словарь, если хотите
Думаю, все объясняют, что происходит. Предлагаю один способ решить эту проблему:
myList = [[1 for i in range(4)] for j in range(3)]
myList[0][0] = 5
print myList
И тогда у вас есть:
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
@spelchekr из умножения списков Python: [[...]] * 3 создает 3 списка, которые зеркально отражают друг друга при изменении, и у меня был тот же вопрос о том, «Почему только внешний * 3 создает больше ссылок, а внутренний - нет. ? Почему не все единицы? "
li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]
ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
Вот мое объяснение после попытки кода выше:
- Внутренний элемент
*3
также создает ссылки, но эти ссылки неизменяемы, что-то вроде того[&0, &0, &0]
, когда нужно изменитьli[0]
, вы не можете изменить любую базовую ссылку const int0
, поэтому вы можете просто изменить адрес ссылки на новый&1
; - while
ma=[&li, &li, &li]
иli
является изменяемым, поэтому при вызовеma[0][0]=1
ma [0] [0] равно to&li[0]
, поэтому все&li
экземпляры изменят свой 1-й адрес на&1
.
Пытаясь объяснить это более описательно,
Операция 1:
x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]
x[0][0] = 1
print(x) # [[1, 0], [0, 0]]
Операция 2:
y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]
y[0][0] = 1
print(y) # [[1, 0], [1, 0]]
Заметили, почему изменение первого элемента первого списка не изменило второй элемент каждого списка? Это потому, что на [0] * 2
самом деле это список из двух чисел, и ссылка на 0 не может быть изменена.
Если вы хотите создать клонированные копии, попробуйте Операцию 3:
import copy
y = [0] * 2
print(y) # [0, 0]
y = [y, copy.deepcopy(y)]
print(y) # [[0, 0], [0, 0]]
y[0][0] = 1
print(y) # [[1, 0], [0, 0]]
еще один интересный способ создания клонированных копий, Операция 4:
import copy
y = [0] * 2
print(y) # [0, 0]
y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]
y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]
Используя встроенную функцию списка, вы можете сделать это следующим образом
a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list
a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number
a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting
a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list
На эти вопросы есть много ответов, я добавляю свой ответ, чтобы пояснить то же самое на диаграмме.
То, как вы создали 2D, создает мелкий список
arr = [[0]*cols]*row
Вместо этого, если вы хотите обновить элементы списка, вы должны использовать
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
Пояснение :
Список можно создать, используя:
arr = [0]*N
или же
arr = [0 for i in range(N)]
В первом случае все индексы массива указывают на один и тот же целочисленный объект
и когда вы присваиваете значение определенному индексу, создается новый объект int, например, arr[4] = 5
создает
Теперь давайте посмотрим, что произойдет, когда мы создадим список списка, в этом случае все элементы нашего верхнего списка будут указывать на один и тот же список.
И если вы обновите значение любого индекса, будет создан новый объект типа int. Но поскольку все индексы списков верхнего уровня указывают на один и тот же список, все строки будут выглядеть одинаково. И вы почувствуете, что при обновлении элемента обновляются все элементы в этом столбце.
Кредиты: Спасибо Пранаву Девараконде за простое объяснение здесь
Я приехал сюда, потому что хотел увидеть, как я могу вложить произвольное количество списков. Выше есть много объяснений и конкретных примеров, но вы можете обобщить N-мерный список списков списков ... с помощью следующей рекурсивной функции:
import copy
def list_ndim(dim, el=None, init=None):
if init is None:
init = el
if len(dim)> 1:
return list_ndim(dim[0:-1], None, [copy.copy(init) for x in range(dim[-1])])
return [copy.deepcopy(init) for x in range(dim[0])]
Вы делаете свой первый вызов функции следующим образом:
dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)
где (3,5,2)
- это кортеж размеров структуры (аналогично shape
аргументу numpy ), и 1.0
- это элемент, которым должна быть инициализирована структура (также работает с None). Обратите внимание, что init
аргумент предоставляется только рекурсивным вызовом для переноса вложенных дочерних списков.
вывод выше:
[[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]]
установить определенные элементы:
l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'
результирующий вывод:
[[[1.0, 1.0], ['abc', 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 56.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [(36+0j), 1.0], [1.0, 1.0], [1.0, 1.0]]]
нетипизированный характер списков продемонстрирован выше
Обратите внимание, что элементы в последовательности не копируются; на них ссылаются несколько раз . Это часто не дает покоя начинающим программистам на Python; учитывать:
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
Произошло то, что [[]]
это одноэлементный список, содержащий пустой список, поэтому все три элемента [[]] * 3
являются ссылками на этот единственный пустой список. Изменение любого из элементов списков изменяет этот единственный список.
Другой пример, объясняющий это, - использование многомерных массивов .
Вы, наверное, пробовали сделать такой многомерный массив:
>>> A = [[**None**] * 2] * 3
Это выглядит правильно, если вы его распечатаете:
>>> A
[[None, None], [None, None], [None, None]]
Но когда вы присваиваете значение, оно появляется в нескольких местах:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
Причина в том, что репликация списка с помощью *
не создает копии, а только создает ссылки на существующие объекты. 3 создает список, содержащий 3 ссылки на один и тот же список длиной два. Изменения в одной строке будут отображаться во всех строках, что почти наверняка не то, что вам нужно.