Python 子列表中意外反映的列表更改列表
我需要用Python创建一个列表列表,因此我键入了以下内容:Python 子列表中意外反映的列表更改列表,python,list,nested-lists,mutable,Python,List,Nested Lists,Mutable,我需要用Python创建一个列表列表,因此我键入了以下内容: myList = [[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]] 然后我更改了其中一个最里面的值: myList[0][0] = 5 现在,我的列表如下所示: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] [
myList = [[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]]
然后我更改了其中一个最里面的值:
myList[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]
三次的列表-而不是内部列表的三个副本,因此,无论何时修改列表(在任何位置),都会看到三次更改
与本例相同:
>>> 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
,而不是评估一次并引用一个列表中的3个
您可能想知道为什么
*
不能像列表理解那样创建独立对象。这是因为乘法运算符*
对对象进行操作,而看不到表达式。当您使用*
将[[1]*4]
乘以3时,*
只会看到1元素列表[[1]*4]
的计算结果,而不是[[1]*4
表达式文本。*
不知道如何复制该元素,也不知道如何重新计算[[1]*4]
,甚至不知道您想要复制,而且通常,甚至可能没有复制元素的方法
*
唯一的选择是对现有子列表进行新的引用,而不是尝试创建新的子列表。其他任何内容都将不一致,或者需要对基本语言设计决策进行重大重新设计
相反,列表理解在每次迭代时都会重新评估元素表达式。[[1]*4代表范围(3)中的n]
每次都会重新评估[1]*4
,原因相同[x**2代表范围(3)]
每次都会重新评估x**2
*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
Aslst[0]
不过是lst1
要获得所需的行为,可以使用列表理解:
lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2
在这种情况下,表达式将针对每个n重新求值,从而生成不同的列表
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
让我们用以下方式重写您的代码:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
然后运行下面的代码,让一切变得更加清晰。代码的作用基本上是打印获得的对象的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]
,也就是说,它创建了一个新的列表,其中将有4个元素,它们是对初始x
对象的引用。净步骤非常类似。基本上是z*3
,即[[x,x,x,x]]*3
并返回[[x,x,x,x],[x,x,x]]
,原因与第一步相同。如果您使用的是python-2.x,请在列表理解范围内,除了正确解释问题的公认答案之外,使用xrange()
,返回一个更高效的生成器(range()
,在python 3中也做同样的工作)\uu
而不是一次性变量n
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
此外,作为一种更具python风格的方法,您可以使用它创建重复元素的迭代器对象:
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]
注意:使用numpy,如果您只想创建一个1或0数组,可以使用np.one
和np.zero
和/或其他数字使用np.repeat()
:
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常见问题解答还包含对这种行为的解释:我想每个人都会解释发生了什么。 我建议一种解决方法:
myList=[[1表示范围(4)中的i]表示范围(3)中的j]
打印myList
然后你有:
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
简单地说,这是因为在python中,一切都是通过引用来工作的,所以当您以这种方式创建列表时,基本上会遇到这样的问题 要解决您的问题,您可以执行以下任一操作: 1.使用numpy数组 2.将列表添加到列表中。
3.如果需要,也可以使用内置列表功能使用字典
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]]
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]]
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]*4 for _ in range(3)]
myList = [[1 for __ in range(4)] for _ in range(3)]
>>> 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
arr = [[0]*cols]*row
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
arr = [0]*N
arr = [0 for i in range(N)]
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
>>> A
[[None, None], [None, None], [None, None]]
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
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]}