Изменения списка списков неожиданно отражаются в подсписках

Oct 27 2008

Мне нужно было создать список списков на 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]]  

а это не то, чего я хотел или ожидал. Может кто-нибудь объяснить, что происходит, и как это обойти?

Ответы

614 CAdaker Oct 27 2008 at 22:03

Когда вы пишете, [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.

140 nadrimajstor Aug 27 2013 at 06:17
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Live Python Tutor Visualize

56 PierreBdR Oct 27 2008 at 22:07

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

Ты пишешь

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, что приводит к другому списку.

38 BlairConrad Oct 27 2008 at 22:02
[[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]]

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

10 Kasravnd Jun 18 2015 at 00:08

Наряду с принятым ответом, который правильно объяснил проблему, в рамках вашего понимания списка, если вы используете 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])
7 ZbyněkWinkler Apr 06 2016 at 20:40

Контейнеры 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 также содержит объяснение этого поведения: Как мне создать многомерный список?

7 jerrymouse Apr 06 2017 at 12:36

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
5 bagrat Jun 10 2015 at 21:38

Давайте перепишем ваш код следующим образом:

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]]по той же причине, что и для первого шага.

5 NeerajKomuravalli Jun 14 2016 at 13:36

Проще говоря, это происходит потому, что в python все работает по ссылке , поэтому, когда вы создаете список списка таким образом, вы в основном сталкиваетесь с такими проблемами.

Чтобы решить вашу проблему, вы можете выполнить любой из них: 1. Используйте документацию по массиву numpy для numpy.empty 2. Добавляйте список по мере того, как вы попадаете в список. 3. Вы также можете использовать словарь, если хотите

4 awulll Apr 24 2016 at 20:31

Думаю, все объясняют, что происходит. Предлагаю один способ решить эту проблему:

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]]
4 ouxiaogu Aug 09 2019 at 15:37

@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 int 0, поэтому вы можете просто изменить адрес ссылки на новый &1;
  • while ma=[&li, &li, &li]и liявляется изменяемым, поэтому при вызове ma[0][0]=1ma [0] [0] равно to &li[0], поэтому все &liэкземпляры изменят свой 1-й адрес на &1.
3 AdilAbbasi Aug 10 2016 at 14:09

Пытаясь объяснить это более описательно,

Операция 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]]
2 AnandTripathi Jul 15 2016 at 20:48

Используя встроенную функцию списка, вы можете сделать это следующим образом

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
2 DeepakPatankar Jun 21 2020 at 18:34

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

То, как вы создали 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. Но поскольку все индексы списков верхнего уровня указывают на один и тот же список, все строки будут выглядеть одинаково. И вы почувствуете, что при обновлении элемента обновляются все элементы в этом столбце.

Кредиты: Спасибо Пранаву Девараконде за простое объяснение здесь

Brian Oct 23 2020 at 02:57

Я приехал сюда, потому что хотел увидеть, как я могу вложить произвольное количество списков. Выше есть много объяснений и конкретных примеров, но вы можете обобщить 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]]]

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

mishsx Nov 23 2020 at 02:45

Обратите внимание, что элементы в последовательности не копируются; на них ссылаются несколько раз . Это часто не дает покоя начинающим программистам на 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 ссылки на один и тот же список длиной два. Изменения в одной строке будут отображаться во всех строках, что почти наверняка не то, что вам нужно.