Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/list/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 子列表中意外反映的列表更改列表_Python_List_Nested Lists_Mutable - Fatal编程技术网

Python 子列表中意外反映的列表更改列表

Python 子列表中意外反映的列表更改列表,python,list,nested-lists,mutable,Python,List,Nested Lists,Mutable,我需要用Python创建一个列表列表,因此我键入了以下内容: my_list = [[1] * 4] * 3 列表如下所示: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] [[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]] 然后我更改了其中一个最里面的值: my_list[0][0] = 5 现在,我的列表如下所示: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

我需要用Python创建一个列表列表,因此我键入了以下内容:

my_list = [[1] * 4] * 3
列表如下所示:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  
然后我更改了其中一个最里面的值:

my_list[0][0] = 5
现在,我的列表如下所示:

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

[[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]]
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
当你写[x]*3的时候,你基本上得到了一个列表[x,x,x]。也就是说,对同一个x有3个引用的列表。然后,当您修改此单个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]*4代表范围3中的n]每次都会重新评估[1]*4,原因相同[x**2代表范围3中的x]每次都会重新评估x**2。每次评估[1]*4都会生成一个新列表,因此列表理解会执行您想要的操作


顺便说一句,[1]*4也不会复制[1]的元素,但这并不重要,因为整数是不可变的。你不能像1.value=2那样把1变成2。

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

你写

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) ]
在这种情况下,表达式将针对每个n重新求值,从而生成不同的列表

size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for _ in range(size)]
使用Python教程:


让我们按照以下方式重写代码:

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

my_list = [z] * 3
然后运行下面的代码,让一切变得更加清晰。代码的作用基本上是打印获得的对象的s,这

返回对象的“标识”

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

print("my_list:")
for i, sub_list in enumerate(my_list):
    print("\t[{}]: {}".format(i, id(sub_list)))
    for j, elem in enumerate(sub_list):
        print("\t\t[{}]: {}".format(j, id(elem)))
您将获得以下输出:

x: 1
y: [1]
z: [1, 1, 1, 1]
my_list:
    [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*4,它将得到一个新的列表z,基本上是[x,x,x,x],也就是说,它创建了一个新的列表,其中将包含4个元素,这些元素是对初始x对象的引用。下一步非常类似。您基本上执行z*3,即[[x,x,x,x]*3并返回[[x,x,x,x],[x,x,x,x]],原因与第一步相同。

与正确解释问题的公认答案一起,而不是使用以下代码创建包含重复元素的列表:

[[1]*4 for _ in range(3)]
此外,还可以使用创建重复元素的迭代器对象:

>>> a = list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0] = 5
>>> a
[5, 1, 1, 1]
注意:如果您使用的是NumPy,并且您只想创建一个1或0数组,您可以使用和/或其他数字使用:


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

>>> 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]]
>>> 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常见问题解答还包含对此行为的解释:

每个人都在解释正在发生的事情。我将建议一种解决方法:

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

my_list[0][0] = 5
print(my_list)
然后你会得到:

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

简单地说,这是因为在python中,一切都是通过引用工作的,所以当您以这种方式创建列表列表时,基本上会遇到这样的问题

要解决您的问题,您可以执行以下任一操作: 1.使用numpy数组 2.将列表添加到列表中。 3.如果需要,也可以使用字典 内置列表函数,您可以这样做

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:

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]]
另一种创建克隆拷贝的有趣方法是操作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]]
my_list=[[1]*4]*3在内存中创建一个list对象[1,1,1,1],并将其引用复制3次。这相当于obj=[1,1,1,1];我的清单=[obj]*3。对obj的任何修改将反映在三个地方,即列表中引用obj的地方。 正确的说法是:

my_list = [[1]*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]。这也可以通过以下方式进行验证:

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

>>> id(my_list[0])
4522139440
>>> id(my_list[1])  # Same as my_list[0]
4522139440
@斯佩尔切克和我有同样的问题 为什么只有外部*3创建更多引用,而内部*3不创建更多引用?为什么不都是1呢

li=[0]*3 打印[李中v的idv][1407241863728、1407241863728、1407241863728、1407241863728] li[0]=1 打印[v-in-li的idv][140724186376014072418637281407241863728] 打印ID0 1407241863728 打印ID 1407241863760 printli[1,0,0] ma=[[0]*3]*3这里主要讨论内部和外部*3 打印[idli for li in ma][1987013355080、1987013355080、1987013355080] ma[0][0]=1 打印[idli for li in ma][1987013355080、1987013355080、1987013355080] printma[[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。
这些问题有很多答案,我加上我的答案,以图表的方式解释同样的问题

创建二维图形的方式会创建一个浅列表

    arr = [[0]*cols]*row
相反,如果要更新列表中的元素,应该使用

   rows, cols = (5, 5) 
   arr = [[0 for i in range(cols)] for j in range(rows)] 

说明:

可以使用以下方法创建列表:

   arr = [0]*N 

在第一种情况下,数组的所有索引都指向同一个整数对象

当您为特定索引赋值时,将创建一个新的int对象,例如arr[4]=5

现在让我们看看创建列表列表时会发生什么,在本例中,我们的顶级列表的所有元素都将指向同一个列表

如果更新任何索引的值,将创建一个新的int对象。但由于所有顶级列表索引都指向同一个列表,因此所有行的外观都相同。您会觉得更新一个元素就是更新该列中的所有元素


信贷:感谢您的简单解释,我来到这里是因为我想看看如何嵌套任意数量的列表。上面有很多解释和具体的例子,但是你可以概括出N维的列表。。。使用以下递归函数:

导入副本 def list\u ndimdim,el=None,init=None: 如果init为None: 初始值=el 如果lendim>1: 返回列表\u ndimdim[0:-1],无,[copy.copyinit for x in rangedim[-1]] 返回[copy.deepcopyinit for x in rangedim[0]] 您对函数的第一次调用如下:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  
尺寸=3,5,2 el=1.0 l=列表\u ndimdim,el 其中,3,5,2是结构维度的元组,类似于numpy shape参数,1.0是您希望用其初始化结构的元素,也可以使用None进行初始化。请注意,init参数仅由递归调用提供,以结转嵌套的子列表

以上各项的产出:

[[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]], [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]], [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]] 设置特定元素:

>>> a = list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0] = 5
>>> a
[5, 1, 1, 1]
l[1][3][1]=56 l[2][2][0]=36.0+0.0j l[0][1][0]=“abc” 结果输出:

x: 1
y: [1]
z: [1, 1, 1, 1]
my_list:
    [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
[1.0,1.0],[abc',1.0],[1.0,1.0],[1.0,1.0],[1.0,1.0], [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 56.0], [1.0, 1.0]], [1.0,1.0]、[1.0,1.0]、[36+0j,1.0]、[1.0,1.0]、[1.0,1.0]] 列表的非类型性质如上文所示

请注意,序列中的项目不会被复制;它们被多次引用。这经常困扰着新的Python程序员;考虑:

>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
实际情况是[[]]是一个包含一个空列表的单元素列表,因此[[]]*3的所有三个元素都是对这个空列表的引用。修改列表的任何元素都会修改此单个列表

另一个解释这一点的例子是使用多维数组< /p> 您可能试图创建如下多维数组:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  
>>>A=[[None]*2]*3 如果您打印它,它看起来是正确的:

>>> A
[[None, None], [None, None], [None, None]]
但当您指定一个值时,它会显示在多个位置:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

原因是使用*复制列表不会创建副本,它只会创建对现有对象的引用。3创建一个列表,其中包含对长度为2的相同列表的3个引用。对一行的更改将显示在所有行中,这几乎肯定不是您想要的。

当原始问题使用乘法运算符构造子列表时,我将添加一个对子列表使用相同列表的示例。添加此答案以确保完整性,因为此问题通常用作此问题的规范

node_count = 4
colors = [0,1,2,3]
sol_dict = {node:colors for node in range(0,node_count)}
每个字典值中的列表都是相同的对象,尝试更改其中一个字典值将在所有字典中看到

>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> [v is colors for v in sol_dict.values()]
[True, True, True, True]
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 2, 3], 2: [0, 2, 3], 3: [0, 2, 3]}
构造字典的正确方法是为每个值使用列表的副本

>>> colors = [0,1,2,3]
>>> sol_dict = {node:colors[:] for node in range(0,node_count)}
>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}

您可以使用is运算符来发现此问题。ls[0]是ls[1]返回True。我很惊讶没有人指出,这里的答案是误导性的。[x] *3存储3个引用,如[x,x,x],仅当x是可变的时才正确。这不适用于例如a=[4]*3,其中在a[0]=5之后,a=[5,4,4]。从技术上讲,这仍然是正确的。[4] *3基本上等于x=4;[x,x,x]。不过,这确实不会导致任何问题,因为4是不可变的。另外,你的另一个例子也并非完全不同。a=[x]*3;[0]=5即使x是可变的,也不会引起问题,因为您不是在修改x,而是在修改a。我不会把我的回答说成是误导或不正确的——如果你处理的是不可变的对象,你就不能打自己的脚。@Allanqi你错了。Do x=1000;lst=[x]*2;lst[0]是lst[1]->True。Python在这里并没有区分可变对象和不可变对象。这只是对这个好答案的一个小小补充:很明显,如果您使用idlst[0][0]和idlst[1][0],甚至是idlst[0]和idlst[1],那么,为什么我们要编写matrix=[[x]*2]不是像你描述的例子那样为同一个对象创建两个元素,它似乎是同一个概念,我缺少了什么?@AhmedMohamed它确实创建了一个列表,其中包含x所指的完全相同对象的两个元素。如果您使用x=object创建一个全局唯一的对象,然后使用matrix=[[x]*2]这些都是真的:matrix[0][0]是matrix[0][1]@nadrimajstor,那么为什么matrix[0]中的更改不会影响matrix[1]就像上面2d matrix的例子一样。@AhmedMohamed在我们的例子中,当你复制一个可变序列时,它是一个列表,因此如果一行=[x]*2,而不是一个矩阵=[row]*2,其中两行都是完全相同的对象,而现在变为一行矩阵[0][0]=y时,会突然反映在另一个矩阵中[0][0]是矩阵[1][0] == True@AhmedMohamed请看一看,因为它可能提供更好的解释这与文字无关。obj[2]=42替换索引2处的引用,而不是对该索引引用的对象进行变异,这就是myList[2][0]=。。。是否myList[2]是一个列表,并且该分配更改列表中索引0处的引用。当然,整数是不可变的,但很多对象类型都是可变的。请注意,[…]列表显示符号也是一种文字语法形式!不要混淆复合对象(如列表)和标量对象(如整数)与可变对象和不可变对象。注意,如果执行第二步,第四步可以省略:a.insert0、[5,1,1]@PranavHosangadi我正在编辑整个页面,而不仅仅是问题。