Изменения в списках, отраженные по всем спискам неожиданно - PullRequest
513 голосов
/ 27 октября 2008

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

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

Ответы [ 12 ]

441 голосов
/ 27 октября 2008

Когда вы пишете [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.

111 голосов
/ 27 августа 2013
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Frames and Objects

Live Python Tutor Visualize

43 голосов
/ 27 октября 2008

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

Вы пишете

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

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

30 голосов
/ 27 октября 2008
[[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 голосов
/ 17 июня 2015

Наряду с принятым ответом, который правильно объяснил проблему, в вашем понимании списка, если вы используете python-2.x, используйте xrange(), который возвращает генератор, который более эффективен (range() в python 3 выполняет ту же работу ) _ вместо одноразовой переменной n:

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

Кроме того, в качестве гораздо более Pythonic способа вы можете использовать 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])
4 голосов
/ 14 июня 2016

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

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

3 голосов
/ 06 апреля 2016

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

2 голосов
/ 06 апреля 2017

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

1 голос
/ 10 августа 2016

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

Операция 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]]
1 голос
/ 24 апреля 2016

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

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