当你写的时候[x]*3
本质上你得到了列表[x, x, x]
。也就是说,一个列表包含 3 个对同一内容的引用x
。当你修改这个单曲时x
通过对它的所有三个引用都可以看到它:
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]*4
每次而不是评估一次并对 1 个列表进行 3 次引用。
你可能想知道为什么*
无法像列表理解那样创建独立的对象。这是因为乘法运算符*
对对象进行操作,无需看到表达式。当你使用*
乘以[[1] * 4]
by 3, *
只能看到 1 元素列表[[1] * 4]
评估为,而不是[[1] * 4
表达文本。*
不知道如何复制该元素,不知道如何重新评估[[1] * 4]
,并且不知道您是否需要副本,而且一般来说,甚至可能没有办法复制该元素。
唯一的选择*
has 是对现有子列表进行新引用,而不是尝试创建新子列表。其他任何事情都会不一致或需要对基本语言设计决策进行重大重新设计。
相反,列表推导式在每次迭代时重新评估元素表达式。[[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。