我创建了一个列表的列表:
>>> xs = [[1] * 4] * 3
>>> print(xs)
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
然后,我改变了最里面的一个值:
>>> xs[0][0] = 5
>>> print(xs)
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
为什么每个子列表的第一个元素都变成了5?
参见:
我如何克隆一个列表,使它不会在分配后意外改变?寻找解决问题的方法
Python:对于字典列表的类似问题,字典列表只存储每次迭代中最后追加的值
如何初始化一个字典,其值是不同的空列表?对于列表字典的类似问题
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)]
or
my_list = [[1 for __ in range(4)] for _ in range(3)]
这里需要注意的重要一点是,*操作符主要用于创建文字列表。虽然1是不可变的,但obj =[1]*4仍然会创建一个重复4次的1的列表,形成[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
>>> 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
让我们按照以下方式重写代码:
x = 1
y = [x]
z = y * 4
my_list = [z] * 3
有了这些,运行下面的代码使一切更清楚。代码所做的基本上是打印所获得的对象的id,这
返回一个对象的“标识符”
并将帮助我们识别它们并分析发生了什么:
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。你的第一步是y * 4,它会得到一个新的列表z,基本上是[x, x, x, x],也就是说,它创建了一个新的列表,它将有4个元素,它们是对初始x对象的引用。下一步非常相似。基本上是z * 3,即[[x, x, x]] * 3,并返回[[x, x, x], [x, x, x], [x, x, x]],与第一步的原因相同。
为了更详细地解释它,
操作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]]
注意,序列中的项不会被复制;它们被多次引用。这经常困扰着新的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的相同列表的引用的列表。对一行的更改将显示在所有行中,这几乎肯定不是您想要的。
除了正确解释问题的可接受答案之外,使用以下代码创建具有重复元素的列表:
[[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]
注:如果你使用NumPy,你只想创建一个数组的1或0,你可以使用np。1和np。0和/或其他数字使用np.repeat:
>>> import numpy as np
>>> np.ones(4)
array([1., 1., 1., 1.])
>>> np.ones((4, 2))
array([[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]])
>>> np.zeros((4, 2))
array([[0., 0.],
[0., 0.],
[0., 0.],
[0., 0.]])
>>> np.repeat([7], 10)
array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])