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


647

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

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

Ответы:


562

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

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

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

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


24
Я удивлен, что никто не указывает на это, ответ здесь вводит в заблуждение. [x]*3Храните 3 ссылки, как [x, x, x]только правильно, когда xизменчиво. Это не работает, например a=[4]*3, где после a[0]=5,a=[5,4,4].
Allanqunzi

42
Технически это все еще правильно. [4]*3по существу эквивалентно x = 4; [x, x, x]. Правда, это никогда не вызовет никаких проблем, так как 4является неизменным. Кроме того, ваш другой пример не совсем другой случай. a = [x]*3; a[0] = 5не вызовет проблем, даже если xон изменчив, так как вы не модифицируете x, а только модифицируете a. Я бы не назвал свой ответ вводящим в заблуждение или неправильным - вы просто не можете выстрелить себе в ногу, если имеете дело с неизменными объектами.
CAdaker

19
@Allanqunzi ты не прав. Делай x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python вообще не различает изменяемые и неизменяемые объекты.
Timgeb

130
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Рамки и объекты

Live Python Tutor Визуализируйте


Итак, почему, если мы напишем matrix = [[x] * 2], не получим 2 elemnts для одного и того же объекта, как в примере, который вы описали, похоже, это та же концепция, что я пропускаю?
Ахмед Мохамед

@AhmedMohamed Действительно, он создает список с двумя элементами одного и того же объекта, на который xссылается. Если вы x = object()matrix = [[x] * 2]matrix[0][0] is matrix[0][1]
создадите

@nadrimajstor, так почему изменение матрицы [0] не влияет на матрицу [1], как в примере выше с 2d матрицей.
Ахмед Мохамед

@AhmedMohamed Сюрприз наступает, когда вы делаете «копию» изменчивой последовательности (в нашем примере это a list), поэтому, если a row = [x] * 2чем a, matrix = [row] * 2где обе строки являются точно одним и тем же объектом, и теперь изменения одной строки matrix[0][0] = yнеожиданно отражаются на другой(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Посмотрите на Неда Батчелдера - Факты и мифы об именах и значениях Python, так как это может дать лучшее объяснение. :)
Надримайстор

52

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

Ты пишешь

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 range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

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


Просто небольшое дополнение к хорошему ответу здесь: очевидно, что вы имеете дело с одним и тем же объектом, если вы делаете id(lst[0][0])и / id(lst[1][0])или даже id(lst[0])иid(lst[1])
Сергей Колодяжный

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

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


3
Вы можете использовать оператор «есть», чтобы обнаружить это. ls [0] is ls [1] возвращает True.
Мипади

9

Наряду с принятым ответом, который правильно объяснил проблему, в вашем понимании списка, если вы используете python-2.x, используйте xrange()возвращающий генератор, который более эффективен ( range()в python 3 выполняет ту же работу) _вместо переменной throwaway 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]

PS Использование 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 содержат ссылки на другие объекты. Смотрите этот пример:

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


6

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повторяется 4 раза в форме [1,1,1,1]. Но если делается какая-либо ссылка на неизменный объект, объект перезаписывается новым.

Это означает, что если мы это сделаем obj[1]=42, то objстанет [1,42,1,1] не так, [42,42,42,42] как некоторые могут предположить. Это также можно проверить:

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440

2
Это не о литералах. obj[2] = 42 заменяет ссылку на индекс 2, в отличие от изменения объекта, на который ссылается этот индекс, что и есть myList[2][0] = ...( myList[2]является списком, и задание изменяет ссылку на индекс 0 в списке). Конечно, целые числа не изменяемые, но множество типов объектов являются . И обратите внимание, что [....]нотация отображения списка также является формой буквального синтаксиса! Не путайте составные (например, списки) и скалярные объекты (например, целые числа) с изменяемыми и неизменяемыми объектами.
Мартин Питерс

5

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

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


2

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

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


2

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

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

2

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

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

2

@spelchekr из списка умножения Python: [[...]] * 3 создает 3 списка, которые отражают друг друга при модификации, и у меня возник тот же вопрос: «Почему только внешний * 3 создает больше ссылок, а внутренний - нет? ? Почему не все 1? "

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

Вот мое объяснение после попытки кода выше:

  • Внутренний *3также создает ссылки, но его ссылки являются неизменными, что-то вроде того [&0, &0, &0], тогда, когда изменить li[0], вы не можете изменить любую основную ссылку const int 0, поэтому вы можете просто изменить адрес ссылки на новый &1;
  • В то время как ma=[&li, &li, &li]и liявляется изменяемым, поэтому, когда вы вызываете ma[0][0]=1, ma [0] [0] равно &li[0], так что все &liэкземпляры будут менять свой 1-й адрес на &1.

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
Обратите внимание, что четвертый шаг можно пропустить, если вы сделаете второй шаг:a.insert(0,[5,1,1,1])
U10-Forward
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.