我需要在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]]
这不是我想要或期望的。有人可以解释一下发生了什么,以及如何绕过它?
答案 0 :(得分:440)
当您撰写[x]*3
时,您基本上会获得列表[x, x, x]
。也就是说,列表中有3个引用相同的x
。然后,当您修改此单个x
时,通过它的所有三个引用都可以看到它。
要解决此问题,您需要确保在每个位置创建新列表。一种方法是
[[1]*4 for _ in range(3)]
每次都会重新评估[1]*4
,而不是评估一次,并对3个列表进行3次引用。
您可能想知道为什么*
不能像列表理解那样创建独立对象。那是因为乘法运算符*
对对象进行操作,而不会看到表达式。当您使用*
将[[1] * 4]
乘以3时,*
只会看到1个元素列表[[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}}之类的操作并将1变为2。
答案 1 :(得分:111)
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
答案 2 :(得分: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重新计算表达式,从而得到不同的列表。
答案 3 :(得分:30)
[[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]]
这可能不那么令人惊讶。
答案 4 :(得分:5)
除了正确解释问题的接受答案之外,在列表理解中,如果您正在使用python-2.x,请使用xrange()
返回更高效的生成器(range()
在python 3中做同样的工作)_
而不是一次性变量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]
P.S。使用numpy,如果您只想创建一个或多个数组,则可以使用np.ones
和np.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])
答案 5 :(得分:4)
简单来说,这种情况正在发生,因为在python中,一切都按引用工作,所以当你以这种方式创建一个列表列表时,你基本上就会遇到这样的问题。
要解决您的问题,您可以执行以下任一操作: 1.使用numpy数组documentation for numpy.empty 2.在到达列表时附加列表。 3.如果需要,您也可以使用字典
答案 6 :(得分: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?
答案 7 :(得分: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]
而不是 。 / p>
[42,42,42,42]
答案 8 :(得分:2)
这些问题有很多答案,我正在添加答案以图解方式说明同样的问题。
创建2D的方式会创建一个浅表
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)]
在第一种情况下,数组的所有索引都指向同一整数对象
,当您将值分配给特定索引时,会创建一个新的int对象,例如arr[4] = 5
创建
现在让我们看看在创建列表列表时会发生什么,在这种情况下,我们顶部列表的所有元素都指向同一列表
如果更新任何索引的值,将创建一个新的int对象。但是,由于所有顶级列表索引都指向同一列表,因此所有行将看起来相同。并且您会感觉到更新元素正在更新该列中的所有元素。
信用:感谢Pranav Devarakonda的简单解释here
答案 9 :(得分: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]]
答案 10 :(得分:1)
让我们以下列方式重写您的代码:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
然后执行此操作,运行以下代码以使一切更清晰。代码的作用基本上是打印获取对象的id
,
返回对象的“身份”
并将帮助我们识别它们并分析会发生什么:
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个元素,这些元素是对初始值的引用{ {1}}对象。净步骤非常相似。您基本上x
z * 3
并返回[[x, x, x, x]] * 3
,原因与第一步相同。
答案 11 :(得分: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]]
答案 12 :(得分:1)
@spelchekr,我对以下问题也有同样的疑问 “为什么只有外部* 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 { {1}},因此您只需将参考地址更改为新的0
; &1
和ma=[&li, &li, &li]
是可变的,所以当您调用li
时,ma [0] [0]等于ma[0][0]=1
,因此所有{{1 }}实例会将其第一个地址更改为&li[0]
。答案 13 :(得分: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
答案 14 :(得分:0)
我到达这里是因为我一直在寻找如何嵌套任意数量的列表。上面有很多解释和具体示例,但是您可以使用以下递归函数来概括...的列表的N维列表:
import copy
def list_ndim(dim, el=None, init=None):
if init is None:
init = el
if len(dim)> 1:
return list_ndim(dim[0:-1], None, [copy.copy(init) for x in range(dim[-1])])
return [copy.deepcopy(init) for x in range(dim[0])]
您是这样第一次调用该函数:
dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)
其中(3,5,2)
是结构尺寸的元组(类似于numpy shape
参数),1.0
是您希望结构初始化的元素(也没有)。请注意,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]]]
设置特定元素:
l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'
结果输出:
[[[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]]]
列表的非类型化性质已在上面演示
答案 15 :(得分:0)
请注意,序列中的项目不会被复制;它们被多次引用。这常常困扰着新的Python程序员。考虑:
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
发生的事情是,[[]]
是一个包含一个空列表的单元素列表,因此[[]] * 3
的所有三个元素都是对该单个空列表的引用。修改列表的任何元素都会修改此单个列表。
另一个说明此问题的示例是使用多维数组。
您可能试图制作一个这样的多维数组:
>>> A = [[**None**] * 2] * 3
如果您打印出来,这看起来是正确的:
>>> A
[[None, None], [None, None], [None, None]]
但是,当您分配一个值时,它会显示在多个位置:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
原因是使用*
复制列表不会创建副本,而只会创建对现有对象的引用。 3创建一个列表,其中包含3个对长度为2的相同列表的引用。更改将显示在所有行中,几乎可以肯定这不是您想要的。
答案 16 :(得分:0)
虽然原始问题使用乘法运算符构造了 sublists,但我将添加一个示例,该示例使用 same 列表作为子列表。添加此答案是为了完整性,因为此问题通常用作该问题的规范
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]}