2008-10-27 11 views
353

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

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

+3

Возможный дубликат с дополнительными пояснениями http://stackoverflow.com/questions/17702937/generating-sublists-using-multiplication-unexpected-behavior – 2013-07-17 15:03:37

ответ

294

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

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

[[1]*4 for n 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.

+1

Спасибо за объяснение и обходной путь. Я все еще удивлен тем, что внешний список заканчивается тем, что содержит три ссылки на анонимный внутренний список. – 2008-10-27 15:40:44

+11

Я удивлен, что ни одно тело не указывает, что ответ здесь вводит в заблуждение. `[x] * 3 хранить 3 ссылки, такие как` [x, x, x] `, только в том случае, когда` x` изменен. Это не работает, например. `a = [4] * 3`, где после` a [0] = 5`, `a = [5,4,4] .` – Allanqunzi 2015-05-22 00:16:41

+19

Технически это все еще правильно. `[4] * 3 существенно эквивалентно` x = 4; [x, x, x] `. Это правда, однако, что это никогда не вызовет никакой * проблемы *, поскольку `4` является неизменным. Кроме того, ваш другой пример - это не совсем другой случай. `a = [x] * 3; a [0] = 5` не вызовет проблем, даже если `x` изменен, поскольку вы не изменяете` x`, а только изменяете `a`. Я бы не описал мой ответ как вводящий в заблуждение или неправильный - вы просто * не можете * стрелять себе в ногу, если вы имеете дело с неизменяемыми объектами. – CAdaker 2015-05-22 08:04:09

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

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

+2

Вы можете использовать оператор «is», чтобы обнаружить это. ls [0] - ls [1] возвращает True. – mipadi 2008-10-27 15:03:52

34

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

Пишешь

lst = [[1] * 4] * 3 

Это эквивалентно:

lst1 = [1]*4 
lst = [lst1]*3 

Это означает lst список с 3-х элементов, указывающих на все lst1. Это означает, что следующие две строки эквивалентны:

lst[0][0] = 5 
lst1[0] = 5 

Как lst[0] ничего, кроме lst1.

Чтобы получить желаемое поведение, вы можете использовать список понимание:

lst = [ [1]*4 for n in xrange(3) ] 

В этом случае выражение повторно оценивается для каждого п, что приводит к различному списку.

+0

Это действительно понятно для новичка, подобного мне. Спасибо! – 2016-04-29 08:18:57

-2

На самом деле, думаю, что в другом случае. Предположим, что если ваш список таков;

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

1

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

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

5

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

Python контейнеры содержат ссылки на другие объекты. Смотрите пример:

>>> a = [] 
>>> b = [a] 
>>> b 
[[]] 
>>> a.append(1) 
>>> b 
[[1]] 

В этом b список, который содержит один элемент, который является ссылкой на список a. Список a изменен.

Умножение списка на целое число эквивалентно добавлению списка к себе несколько раз (см. common sequence operations). Так продолжая пример:

>>> c = b + b 
>>> c 
[[1], [1]] 
>>> 
>>> a[0] = 2 
>>> c 
[[2], [2]] 

Мы можем видеть, что список c теперь содержит две ссылки на список a что эквивалентно c = b * 2.

Python FAQ также содержит объяснение такого поведения: How do I create a multidimensional list?

1

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

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

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

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

1

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

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 
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 не может быть изменена.

Если вы хотите создать клон копии, попробуйте Operation 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

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