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

StackOverflow https://stackoverflow.com/questions/240178

Вопрос

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

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

[[1]*4 for _ in range(3)]

который будет переоценивать [1] * 4 каждый раз вместо того, чтобы оценивать его один раз и делать 3 ссылки на 1 список.

<Ч>

Вы можете задаться вопросом, почему * не может создавать независимые объекты так, как это делает понимание списка. Это потому, что оператор умножения * работает с объектами, не видя выражений. Когда вы используете * для умножения [[1] * 4] на 3, * видит только 1-элементный список [[ 1] * 4] , а не текст выражения [[1] * 4 . * не знает, как сделать копии этого элемента, не знает, как переоценить [[1] * 4] , и даже не подозревает, что вам даже нужны копии, и вообще , может даже не быть способа скопировать элемент.

Единственная опция * - создавать новые ссылки на существующий подсписок, а не пытаться создавать новые подсписки. Все остальное будет противоречивым или потребует серьезной доработки фундаментальных решений по языку.

Напротив, понимание списка переоценивает выражение элемента на каждой итерации. [[1] * 4 для n в диапазоне (3)] переоценивает [1] * 4 каждый раз по той же причине [x ** 2 для x в диапазоне (3)] каждый раз переоценивает x ** 2 . Каждая оценка [1] * 4 генерирует новый список, поэтому понимание списка делает то, что вы хотели.

Кстати, [1] * 4 также не копирует элементы [1] , но это не имеет значения, поскольку целые числа являются неизменяемыми. Вы не можете сделать что-то вроде 1.value = 2 и превратить 1 в 2.

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

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

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

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

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

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

myList = [z] * 3

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

  

Верните & # 8220; личность & # 8221; объекта

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

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 для i в диапазоне (4)] для j в диапазоне (3)]

myList[0][0] = 5

print myList

И тогда у вас есть:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top