列表列表更改意外地反映在子列表中

2022-09-05 01:16:36

我创建了一个列表列表:

xs = [[1] * 4] * 3

# xs == [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  

然后,我更改了最里面的值之一:

xs[0][0] = 5

# xs == [[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  

为什么每个子列表的第一个元素都更改为 ?5


答案 1

当你写的时候,你会得到,基本上,列表。也就是说,具有 3 个对相同 .然后,当您修改此单曲时,可以通过对它的所有三个引用来查看它:[x]*3[x, x, x]xx

x = [1] * 4
xs = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(xs[0]): {id(xs[0])}\n"
    f"id(xs[1]): {id(xs[1])}\n"
    f"id(xs[2]): {id(xs[2])}"
)
# id(xs[0]): 140560897920048
# id(xs[1]): 140560897920048
# id(xs[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"xs: {xs}")
# xs: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

要解决此问题,您需要确保在每个位置创建一个新列表。一种方法是这样做

[[1]*4 for _ in range(3)]

每次都会重新评估,而不是评估一次,并对1个列表进行3次引用。[1]*4


您可能想知道为什么不能像列表理解那样制作独立对象。这是因为乘法运算符对对象进行操作,而看不到表达式。当您用于乘以 3 时,只看到 1 元素列表的计算结果为,而不是表达式文本。 不知道如何制作该元素的副本,不知道如何重新评估,甚至不知道你甚至想要副本,一般来说,甚至可能没有办法复制该元素。***[[1] * 4]*[[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变成2。[1] * 4[1]1.value = 2


答案 2
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for _ in range(size)]

使用Python Tutor的实时可视化

Frames and Objects