关于python:意外反映在子列表中的列表更改列表

List of lists changes reflected across sublists unexpectedly

我需要在python中创建一个列表,所以我键入了以下内容:

1
myList = [[1] * 4] * 3

列表如下:

1
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

然后我改变了最里面的一个值:

1
myList[0][0] = 5

现在我的列表如下:

1
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

这不是我想要或期望的。有人能解释一下发生了什么事,以及如何避开它吗?


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

要修复它,您需要确保在每个位置创建一个新列表。一种方法是

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

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

你可能想知道为什么*不能像列表理解那样生成独立的对象。这是因为乘法运算符*操作对象,而看不到表达式。当使用*[[1] * 4]乘以3时,*只看到一个元素列表[[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
2
3
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Frames and Objects

实时python教程可视化


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

你写

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

这相当于:

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

这意味着lst是一个包含3个元素的列表,所有元素都指向lst1。这意味着以下两行是等效的:

1
2
lst[0][0] = 5
lst1[0] = 5

因为lst[0]只是lst1而已。

要获得所需的行为,可以使用列表理解:

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

在这种情况下,将为每个n重新计算表达式,从而得到不同的列表。


1
[[1] * 4] * 3

甚至:

1
[[1, 1, 1, 1]] * 3

创建一个引用内部[1,1,1,1]3次的列表-而不是内部列表的三个副本,因此,每当修改列表(在任何位置)时,都会看到三次更改。

与本例相同:

1
2
3
4
5
6
7
>>> 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(),它返回一个更高效的生成器(python 3中的range()执行相同的任务)_,而不是一次性变量n

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

此外,作为一种更为pythonic的方法,您可以使用itertools.repeat()创建重复元素的迭代器对象:

1
2
3
4
5
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

p.s.使用numpy,如果您只想创建一个1或0的数组,您可以使用np.onesnp.zeros和/或其他数字使用np.repeat()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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。在到达列表时附加该列表。三。如果你想的话,你也可以用字典。


myList = [[1]*4] * 3在内存中创建一个列表对象[1,1,1,1]并复制其引用3次。这相当于obj = [1,1,1,1]; myList = [obj]*3。对obj的任何修改将反映在三个地方,列表中引用obj的地方。正确的陈述应该是:

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

1
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]->strike>。


python容器包含对其他对象的引用。请参见此示例:

1
2
3
4
5
6
7
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

在这个目录中,b是一个列表,其中包含一个引用列表a的项目。清单a是可变的。

一个列表乘以一个整数等于将该列表多次添加到其自身中(请参见公共序列操作)。所以继续这个例子:

1
2
3
4
5
6
7
>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

我们可以看到,列表c现在包含两个对列表a的引用,相当于c = b * 2

python faq还包含对这种行为的解释:如何创建多维列表?


试图用更具描述性的方式来解释,

操作1:

1
2
3
4
5
6
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:

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
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]]

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

myList = [[1 for i in range(4)] for j in range(3)]

1
myList[0][0] = 5

print myList

然后你有:

1
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

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

1
2
3
4
5
x = 1
y = [x]
z = y * 4

myList = [z] * 3

然后,运行下面的代码,使一切更加清晰。代码所做的基本上是打印所获得对象的id,这是

Return the"identity" of an object

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

1
2
3
4
5
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)))

您将得到以下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,还有一个包含x的单元素列表y。你的第一步是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]],原因与第一步相同。


通过使用内置列表函数,您可以这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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