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

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

回答1:

当您编写[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,而不是对其进行一次评估并对1个列表进行3次引用。


您可能想知道为什么*不能像列表理解那样创建独立的对象。这是因为乘法运算符*在对象上运行,而没有看到表达式。当您使用*[[1]*4]乘以3时,*仅看到1元素列表[[1] * 4] 而不是[[[1]*4]表达式文本。 *不知道如何制作该元素的副本,不知道如何重新评估[[1]*4],也不知道您甚至想要复制, ,甚至可能没有办法复制元素。

唯一的选项*是对现有子列表进行新引用,而不是尝试创建新的子列表。任何其他事情都将前后矛盾或需要对基础语言设计决策进行重大重新设计。

相反,列表推导会在每次迭代时重新评估元素表达式。 [[1]*4forninrange(3)]出于相同的原因,每次重新评估[1]*4出于同一原因[x**2forx在range(3)中)每次都会重新评估x**2。每个[1]*4的求值都会生成一个新列表,因此列表理解功能可以满足您的要求。

顺便说一句,[1]*4也不复制[1]的元素,但这并不重要,因为整数是不可变的。您不能执行1.value=2这样的操作并将1变成2。

回答2:

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

实时Python导师可视化

回答3:

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

您写

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

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

回答4:

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

在可以理解列表的正确答案旁边,如果您使用的是python-2.x,请使用xrange()返回可以更有效地生成器的列表理解(python 3中的range()做同样的工作)_而不是一次性变量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,如果您只想创建一个由1或0组成的数组,则可以使用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])

回答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常见问题解答也包含此行为的解释:如何创建一个多维列表?

回答7:

简而言之,这是因为在python中,所有内容通过引用都有效,因此,当您以这种方式创建列表列表时,基本上会遇到此类问题。

要解决您的问题,您可以执行以下任一操作:1.使用numpy数组有关numpy.empty的文档。2.进入列表后,追加列表。 3.如果需要,也可以使用字典

回答8:

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] (可能有人会假设。)

回答9:

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

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

myList = [z] * 3

然后,运行以下代码以使所有内容更加清晰。该代码的作用基本上是打印 id s个获得的对象,

返回对象的"身份"

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

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)和一个包含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]] ,其原因与第一步相同。

回答10:

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

myList=[[1代表范围(4)中的i]代表j范围(3)中的]]

myList[0][0] = 5

打印myList

然后您有:

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

回答11:

试图更描述性地解释它,

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

回答12:

来自 Python列表的

@spelchekr乘法:[[...]] * 3使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

回答13:

通过使用内置列表功能,您可以这样做

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
回到顶部