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

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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
731
0
44 799
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

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

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

Вы можете использовать оператор «is», чтобы обнаружить это. ls [0] is ls [1] возвращает True.

mipadi 27.10.2008 18: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.

Я удивлен, что никто не указывает на это, ответ здесь вводит в заблуждение. [x]*3 store 3 ссылки, такие как [x, x, x], верны только тогда, когда x является изменяемым. Это не работает, например, для a=[4]*3, где после a[0]=5, a=[5,4,4].

Allanqunzi 22.05.2015 03:16

Технически все еще правильно. [4]*3 по существу эквивалентен x = 4; [x, x, x]. Правда, это никогда не вызовет проблема, поскольку 4 неизменяем. Кроме того, ваш другой пример не совсем другой случай. a = [x]*3; a[0] = 5 не вызовет проблем, даже если x является изменяемым, поскольку вы не модифицируете x, а только модифицируете a. Я бы не назвал свой ответ вводящим в заблуждение или неверным - вы просто не могу простреливаете себе ногу, если имеете дело с неизменяемыми объектами.

CAdaker 22.05.2015 11:04

@Allanqunzi, ты ошибаешься. Сделайте x = 1000; lst = [x]*2; lst[0] is lst[1] -> True. Python здесь вообще не делает различий между изменяемыми и неизменяемыми объектами.

timgeb 17.04.2016 21:08

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

Ты пишешь

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

Просто небольшое дополнение к красивому ответу: очевидно, что вы имеете дело с одним и тем же объектом, если используете id(lst[0][0]) и id(lst[1][0]) или даже id(lst[0]) и id(lst[1]).

Sergiy Kolodyazhnyy 17.05.2017 10:08
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Frames and Objects

Live Python Tutor Visualize

Итак, почему, если мы напишем матрицу = [[x] * 2], не создаст 2 элемента для одного и того же объекта, как в примере, который вы описываете, это похоже на ту же концепцию, что мне не хватает?

Ahmed Mohamed 01.07.2017 20:55

@AhmedMohamed Действительно, он составляет список из двух элементов одного и того же объекта, на который ссылается x. Если вы создаете глобально уникальный объект с помощью x = object(), а затем делаете matrix = [[x] * 2], это действительно так: matrix[0][0] is matrix[0][1]

nadrimajstor 02.07.2017 16:13

@nadrimajstor, так почему же изменение в матрице [0] не влияет на матрицу [1], как в примере выше с 2-мерной матрицей.

Ahmed Mohamed 02.07.2017 16:31

@AhmedMohamed Сюрприз возникает, когда вы делаете «копию» изменяемой последовательности (в нашем примере это list), поэтому, если row = [x] * 2, а не matrix = [row] * 2, где обе строки являются одним и тем же объектом, и теперь изменяется на одну строку, matrix[0][0] = y внезапно отражается в другой (matrix[0][0] is matrix[1][0]) == True

nadrimajstor 02.07.2017 18:44

@AhmedMohamed Взгляните на Нед Батчелдер - Факты и мифы об именах и ценностях Python, поскольку он может предложить лучшее объяснение. :)

nadrimajstor 02.07.2017 19:11

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

x = 1
y = [x]
z = y * 4

myList = [z] * 3

Затем запустите следующий код, чтобы все стало более понятным. Код в основном печатает id полученных объектов, которые

Return the “identity” of an object

и поможет нам их выявить и проанализировать, что происходит:

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-2.x, используйте xrange(), который возвращает генератор, который является более эффективным (range() в python 3 выполняет ту же работу) _ вместо одноразовой переменной 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]

P.S. Используя 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 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]]

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

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

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

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

Обратите внимание, что четвертый шаг может быть пропущен, если вы сделаете второй шаг: a.insert(0,[5,1,1,1])

U11-Forward 19.10.2018 08:29

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

Операция 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]]

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

Дело не в литералах. obj[2] = 42заменяет ссылку с индексом 2, в отличие от изменения объекта, на который ссылается этот индекс, что и делает myList[2][0] = ... (myList[2] - это список, а назначение изменяет ссылку с индексом 0 в этом списке). Конечно, целые числа неизменяемы, но много типов объектов являются. И обратите внимание, что отображение списка [....] также является формой буквального синтаксиса! Не путайте составные (например, списки) и скалярные объекты (например, целые числа) с изменяемыми и неизменяемыми объектами.

Martijn Pieters 25.07.2018 18:52

@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;
  • в то время как ma=[&li, &li, &li] и li являются изменяемыми, поэтому при вызове ma[0][0]=1 ma [0] [0] равно &li[0], поэтому все экземпляры &li изменят свой 1-й адрес на &1.

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

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

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

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

Другие вопросы по теме