Изменения в списках, отраженные по всем спискам неожиданно
Мне нужно было создать список списков в 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]]
что не то, что я хотел или ожидал. Может кто-нибудь объяснить, пожалуйста, что происходит, и как обойти это?
18 ответов
Когда ты пишешь [x]*3
вы получите, по сути, список [x, x, x]
, То есть список с 3 ссылками на один и тот же x
, Когда вы затем измените этот сингл x
это видно по всем трем ссылкам на него.
Чтобы исправить это, вам нужно убедиться, что вы создаете новый список в каждой позиции. Один из способов сделать это
[[1]*4 for n in range(3)]
который будет переоценивать [1]*4
каждый раз вместо того, чтобы оценивать его один раз и делать 3 ссылки на 1 список.
Вы можете спросить, почему *
не может создавать независимые объекты так, как это делает понимание списка. Это потому, что оператор умножения *
действует на объекты, не видя выражений. Когда вы используете *
чтобы умножить [[1] * 4]
на 3, *
видит только 1-элементный список [[1] * 4]
оценивает, а не [[1] * 4
текст выражения. *
понятия не имеет, как сделать копии этого элемента, понятия не имеет, как переоценить [[1] * 4]
и даже не подозреваю, что вам даже нужны копии, и вообще, может даже не быть способа скопировать элемент.
Единственный вариант *
has должен делать новые ссылки на существующий подсписок вместо того, чтобы пытаться создавать новые подсписки. Все остальное будет противоречивым или потребует серьезного изменения основных решений по проектированию языка.
Напротив, понимание списка переоценивает выражение элемента на каждой итерации. [[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)]
На самом деле, это именно то, что вы ожидаете. Давайте разложим то, что здесь происходит:
Ты пишешь
lst = [[1] * 4] * 3
Это эквивалентно:
lst1 = [1]*4
lst = [lst1]*3
Это означает lst
это список с 3 элементами, указывающими на lst1
, Это означает, что две следующие строки эквивалентны:
lst[0][0] = 5
lst1[0] = 5
Как lst[0]
ничего кроме lst1
,
Чтобы получить желаемое поведение, вы можете использовать понимание списка:
lst = [ [1]*4 for n in xrange(3) ]
В этом случае выражение переоценивается для каждого 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]]
где это, вероятно, немного менее удивительно.
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,1,1,1]
где каждый 1
является атомным, а не ссылкой 1
повторяется 4 раза. Это означает, что если мы делаем obj[2]=42
, затем obj
станет [1,1,42,1]
не [42,42,42,42]
как некоторые могут предположить.
Наряду с принятым ответом, который правильно объяснил проблему, в пределах вашего понимания списка, если вы используете python-2.x, используйте xrange()
который возвращает генератор, который является более эффективным (range()
в питоне 3 делает ту же работу) _
вместо одноразовой переменной n
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
Кроме того, как гораздо более Pythonic способ вы можете использовать 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 также содержат объяснение этого поведения: как мне создать многомерный список?
На эти вопросы есть много ответов, я добавляю свой ответ, чтобы пояснить то же самое на диаграмме.
То, как вы создали 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. Но поскольку все индексы списка верхнего уровня указывают на один и тот же список, все строки будут выглядеть одинаково. И вы почувствуете, что при обновлении элемента обновляются все элементы в этом столбце.
Кредиты: Спасибо Пранаву Девараконде за простое объяснение здесь
Проще говоря, это происходит потому, что в python все работает по ссылке, поэтому, когда вы создаете список списков таким образом, вы в основном сталкиваетесь с такими проблемами.
Чтобы решить вашу проблему, вы можете сделать одно из них: 1. Используйте документацию для numpy array для numpy.empty 2. Добавьте список по мере его появления. 3. Вы также можете использовать словарь, если хотите
Давайте перепишем ваш код следующим образом:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
Затем, выполнив следующий код, сделайте все более понятным. То, что делает код, это в основном распечатать id
с полученных объектов, которые
Вернуть "идентичность" объекта
и поможет нам идентифицировать их и проанализировать, что происходит:
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]]
по той же причине, что и для первого шага.
Я думаю, что все объясняют, что происходит. Я предлагаю один из способов ее решения:
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
; - пока
ma=[&li, &li, &li]
а такжеli
изменчив, поэтому, когда вы вызываетеma[0][0]=1
, ma[0][0] равно&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
В то время как исходный вопрос создавал подсписки с оператором умножения, я добавлю пример, в котором тот же список используется для подсписок. Добавляем этот ответ для полноты, так как этот вопрос часто используется как канонический для проблемы
node_count = 4
colors = [0,1,2,3]
sol_dict = {node:colors for node in range(0,node_count)}
Список в каждом значении словаря является одним и тем же объектом, попытка изменить одно из значений словаря будет видна во всех.
>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> [v is colors for v in sol_dict.values()]
[True, True, True, True]
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 2, 3], 2: [0, 2, 3], 3: [0, 2, 3]}
Правильный способ создания словаря - использовать копию списка для каждого значения.
>>> colors = [0,1,2,3]
>>> sol_dict = {node:colors[:] for node in range(0,node_count)}
>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
Я приехал сюда, потому что хотел увидеть, как я могу вложить произвольное количество списков. Выше есть много объяснений и конкретных примеров, но вы можете обобщить 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)
представляет собой набор размеров структуры (аналогично numpy
shape
аргумент), и
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 ссылки на один и тот же список длины два. Изменения в одной строке будут отображаться во всех строках, что почти наверняка не то, что вам нужно.
На самом деле, подумайте об этом в другом случае. Предположим, что если ваш список это;
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
и если вы напишите myList[0][0] = 5
выходной будет;
>>>
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>>
Как вы и ожидали. Но так как вы определяете свою переменную списка следующим образом;
[[1] * 4] * 3
Python обработает ваши коды по этому шаблону. Так что если вы напишите myList[0][0]
и ваш список определен как выше, Python будет обрабатывать его как [1]*3
, Вот почему все списки первых элементов изменены.