列表更改意外地反映在子列表中

问题描述 投票:509回答:12

我需要在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]]  

这不是我想要或期望的。有人可以解释一下发生了什么,以及如何解决这个问题?

python list nested-lists mutable
12个回答
452
投票

当你写[x]*3时,你基本上得到了[x, x, x]这个列表。也就是说,列表中有3个引用相同的x。然后,当您修改此单个x时,通过对它的所有三个引用都可以看到它。

要解决此问题,您需要确保在每个位置创建新列表。一种方法是

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

这将每次重新评估[1]*4,而不是评估它一次,并对3个列表进行3次引用。


你可能想知道为什么*不能像列表理解那样制作独立的对象。这是因为乘法运算符*对对象进行操作,而不会看到表达式。当你使用*[[1] * 4]乘以3时,*只会看到[[1] * 4]评估的1元素列表,而不是[[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
投票

我猜每个人都在解释发生了什么。我建议一种解决方法:

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

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

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

114
投票
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Live Python Tutor Visualize


43
投票

实际上,这正是您所期望的。让我们分解这里发生的事情:

你写

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重新计算表达式,从而得到不同的列表。


31
投票
[[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]]

它可能不那么令人惊讶。


6
投票

除了正确解释问题的接受答案,在你的列表理解中,如果你正在使用python-2.x使用xrange()返回一个更高效的生成器(python 3中的range()做同样的工作)_而不是一次性变量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]

附:使用numpy,如果你只想创建一个或零的数组,你可以使用np.onesnp.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
投票

简单来说,这种情况正在发生,因为在python中一切都通过引用工作,所以当你创建一个列表列表时,你基本上会遇到这样的问题。

要解决您的问题,您可以执行以下任一操作:1。使用numpy数组documentation for numpy.empty 2.在到达列表时附加列表。如果你愿意,你也可以使用字典


3
投票

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?


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] 有些人可能认为。


1
投票

让我们以下列方式重写您的代码:

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

myList = [z] * 3

然后,运行以下代码,使一切更清晰。代码的作用基本上是打印获取对象的ids

返回对象的“标识”

并将帮助我们识别它们并分析发生的情况:

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]],原因与第一步相同。

© www.soinside.com 2019 - 2024. All rights reserved.