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

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

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

Ответ 1

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

Чтобы это исправить, вам нужно убедиться, что вы создаете новый список в каждой позиции. Один из способов сделать это

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

Ответ 3

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

Вы пишете

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 xrange(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]]

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

Ответ 5

Наряду с принятым ответом, который правильно объяснил проблему, в понимании вашего списка, если вы используете 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]

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

Ответ 6

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

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

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

Ответ 8

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], как могут предположить некоторые.

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

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

еще один интересный способ создания копий клонов, Operation 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]]

Ответ 12

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

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

Ответ 13

Собственно, подумайте об этом в другом случае. Предположим, что если ваш список таков:

[[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. Вот почему все списки первых элементов изменены.