Изменения в списках неожиданно отражаются в подсписках
-
04-07-2019 - |
Вопрос
Мне нужно было создать список списков на 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.
Другие советы
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
[[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. Вы также можете использовать словарь, если хотите
Контейнеры 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 также содержат объяснения этого поведения: Как мне создать многомерный список?
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