Cha*_*son 576 python list mutable nested-lists
我需要在Python中创建一个列表列表,所以我输入以下内容:
myList = [[1] * 4] * 3
Run Code Online (Sandbox Code Playgroud)
列表看起来像这样:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Run Code Online (Sandbox Code Playgroud)
然后我改变了最里面的一个值:
myList[0][0] = 5
Run Code Online (Sandbox Code Playgroud)
现在我的列表看起来像这样:
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
Run Code Online (Sandbox Code Playgroud)
这不是我想要或期望的.有人可以解释一下发生了什么,以及如何解决这个问题?
CAd*_*ker 501
当你写作时,[x]*3你基本上得到了这个列表[x, x, x].也就是说,列表中有3个引用相同的列表x.然后,当您修改此单个时x,通过对它的所有三个引用都可以看到它.
要解决此问题,您需要确保在每个位置创建新列表.一种方法是
[[1]*4 for _ in range(3)]
Run Code Online (Sandbox Code Playgroud)
这将[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变为2.
小智 118
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
Run Code Online (Sandbox Code Playgroud)

Pie*_*BdR 46
实际上,这正是您所期望的.让我们分解这里发生的事情:
你写
lst = [[1] * 4] * 3
Run Code Online (Sandbox Code Playgroud)
这相当于:
lst1 = [1]*4
lst = [lst1]*3
Run Code Online (Sandbox Code Playgroud)
这意味着lst是一个包含3个元素的列表lst1.这意味着以下两行是等效的:
lst[0][0] = 5
lst1[0] = 5
Run Code Online (Sandbox Code Playgroud)
就像lst[0]什么一样lst1.
要获得所需的行为,您可以使用列表理解:
lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2
Run Code Online (Sandbox Code Playgroud)
在这种情况下,对每个n重新计算表达式,从而得到不同的列表.
Bla*_*rad 33
[[1] * 4] * 3
Run Code Online (Sandbox Code Playgroud)
甚至:
[[1, 1, 1, 1]] * 3
Run Code Online (Sandbox Code Playgroud)
创建一个引用内部[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]]
Run Code Online (Sandbox Code Playgroud)
它可能不那么令人惊讶.
my_list = [[1]*4] * 3[1,1,1,1]在内存中创建一个列表对象并将其引用复制 3 次。这相当于obj = [1,1,1,1]; my_list = [obj]*3. 对 的任何修改obj都将反映在obj列表中引用的三个位置。正确的说法是:
my_list = [[1]*4 for _ in range(3)]
Run Code Online (Sandbox Code Playgroud)
或者
my_list = [[1 for __ in range(4)] for _ in range(3)]
Run Code Online (Sandbox Code Playgroud)
这里要注意的重要一点是,*运算符主要用于创建文字列表。虽然1是不可变的,obj = [1]*4但仍然会创建一个1重复 4 次以上的列表来形成[1,1,1,1]. 但是,如果对不可变对象进行了任何引用,则该对象将被一个新对象覆盖。
这意味着如果我们这样做obj[1] = 42,那么obj将变得[1,42,1,1] 不像 某些人可能假设的那样。这也可以验证:[42,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
Run Code Online (Sandbox Code Playgroud)
>>> my_list[1] = 42 # Since my_list[1] is immutable, this operation overwrites my_list[1] with a new object changing its id.
>>> my_list
[1, 42, 1, 1]
>>> id(my_list[0])
4522139440
>>> id(my_list[1]) # id changed
4522140752
>>> id(my_list[2]) # id still same as my_list[0], still referring to value `1`.
4522139440
Run Code Online (Sandbox Code Playgroud)
Python 容器包含对其他对象的引用。看这个例子:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
Run Code Online (Sandbox Code Playgroud)
这b是一个列表,其中包含一项是对 list 的引用a。该列表a是可变的。
列表乘以整数相当于将列表多次添加到自身(参见常见序列操作)。所以继续这个例子:
>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]
Run Code Online (Sandbox Code Playgroud)
我们可以看到列表c现在包含两个对列表的引用,a相当于c = b * 2。
Python FAQ 还包含对此行为的解释:如何创建多维列表?
我添加我的答案以图解方式解释相同的内容。
创建 2D 的方式创建了一个浅列表
arr = [[0]*cols]*row
Run Code Online (Sandbox Code Playgroud)
相反,如果您想更新列表的元素,您应该使用
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
Run Code Online (Sandbox Code Playgroud)
解释:
可以使用以下方法创建一个列表:
arr = [0]*N
Run Code Online (Sandbox Code Playgroud)
或者
arr = [0 for i in range(N)]
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,数组的所有索引都指向同一个整数对象
当您为特定索引分配值时,会创建一个新的 int 对象,例如arr[4] = 5创建
现在让我们看看当我们创建一个列表列表时会发生什么,在这种情况下,顶部列表的所有元素都将指向同一个列表
如果您更新任何索引的值,将创建一个新的 int 对象。但由于所有顶级列表索引都指向同一个列表,因此所有行看起来都相同。你会感觉更新一个元素就是更新该列中的所有元素。
致谢:感谢Pranav Devarakonda的简单解释
除了正确解释问题的接受答案之外,在列表理解中,如果使用python-2.x xrange(),则返回一个更有效的生成器(range()在python 3中执行相同的工作)_而不是一次性变量n:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
Run Code Online (Sandbox Code Playgroud)
此外,作为更多Pythonic方式,您可以使用它itertools.repeat()来创建重复元素的迭代器对象:
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]
Run Code Online (Sandbox Code Playgroud)
PS使用numpy的,如果你只是想创建1或0,你可以使用数组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])
Run Code Online (Sandbox Code Playgroud)
让我们按以下方式重写您的代码:
x = 1
y = [x]
z = y * 4
my_list = [z] * 3
Run Code Online (Sandbox Code Playgroud)
然后有了这个,运行以下代码以使一切更加清晰。代码所做的基本上是打印id获得的对象的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)))
Run Code Online (Sandbox Code Playgroud)
您将获得以下输出:
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
Run Code Online (Sandbox Code Playgroud)
所以现在让我们一步一步来。你有xwhich is1和一个y包含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], [x, x, x, x]],原因与第一步相同。
小智 5
简而言之,发生这种情况是因为在 python 中,一切都是通过引用工作的,所以当你创建一个列表列表时,你基本上会遇到这样的问题。
要解决您的问题,您可以执行以下任一操作:
| 归档时间: |
|
| 查看次数: |
27815 次 |
| 最近记录: |