这是因为您只创建了一个内部列表对象并对其进行了更改。
在伪代码中,您正在做的是:
- 创建一个名为
line 的列表,并为其分配[None, None, None]
- 创建一个名为
lines的空列表
- 三回:
-- 从square 列表中选择 n 个项目
-- 将这三个项目分配给line[0]、line[1]和line[2]
-- 将line 附加到lines
所以,您正在做的是分配给line 的个别项目。这很重要 - 您不是每次都创建一个新对象,而是更改 line 列表中的各个项目。
最后,line 将指向列表[7, 8, 9]。您可以看到lines 实质上是[line, line, line](相同对象的三倍的列表),所以现在它具体指向[[7,8,9], [7,8,9], [7,8,9]]。
要解决这个问题,最能保留原始代码的解决方案可能是在添加 line 后重新定义它。这样,变量名line 每次都会引用不同的列表,就不会有这个问题了。
def getLines(square, N):
i = 0
line = [None]*N
lines = list()
for elt in square:
line[i] = elt
i += 1
if i == N:
lines.append(line)
line = [None]*N # Now `line` points to a different object
i = 0
return lines
当然,有更精简、更 Pythonic 的代码可以做同样的事情(我看到已经给出了答案)。
编辑 - 好的,这里有一个更详细的解释。
也许其中一个关键概念是列表不是其他对象的容器。它们仅包含对其他对象的引用。
另一个关键概念是,当您更改列表中的项目(项目分配)时,您不会使整个列表对象成为另一个对象。您只是在更改其中的引用。这是我们在很多情况下认为理所当然的事情,但是当我们希望事情朝着相反的方向发展并“回收”一个列表时,不知何故变得违反直觉。
正如我在 cmets 中所写的那样,如果 list 是一只名为 Fluffy 的猫,那么每次添加时都会创建一个指向 Fluffy 的镜像。所以你可以给 Fluffy 戴一顶派对帽,放一面镜子指向它,然后给 Fluffy 一个小丑鼻子,戴上另一面镜子,然后把 Fluffy 打扮成芭蕾舞演员,再加上第三面镜子,当你照镜子时,所有其中三个将展示芭蕾舞演员蓬松。 (对不起蓬松)。
我的意思是,实际上在您的第一个脚本中,当您执行附加操作时:
lines.append(line)
根据我提到的第一个概念,您没有将lines 包含 line 的当前状态作为一个单独的对象。您正在附加对line 列表的引用。
当你这样做时,
line[i] = elt
根据第二个概念,当然line 总是同一个对象;您只是在更改在 i-th 位置引用的内容。
这就是为什么在您的脚本末尾,lines 会显示为“包含三个相同的对象”:因为您实际上将三个引用附加到同一个对象。当您要求查看lists 的内容时,您将阅读三遍list 对象的当前状态。
在我上面提供的代码中,我重新定义了名称 lists 以使其在每次附加到 lists 时引用一个全新列表:
lines.append(line)
line = [None]*N # Now `line` points to a different object
这样,在脚本的末尾我附加了“三只不同的猫”,每只都方便地命名为 Fluffy,直到我添加它,以便为之后的新 Fluffy 列表留出空间。
现在,在您的第二个脚本中,您执行类似的操作。关键指令是:
lines = [[None]*N]*N # Need to be initialized to be able to index it.
在这一行中,您正在创建两个对象:
- 列表[None, None, None]
- 名为lines 的列表,其中包含对同一列表[None, None, None] 的N 个引用。
你所做的只是直接创造出 Fluffy 和指向他的三面镜子。
事实上,如果您更改 lines[0][2] 或 lines[1][2],您只是更改了同一个 Fluffy 的同一个项目 [2]。
你真正想做的是,
lines = [[None]*N for i in range(N)]
它创建了三只不同的猫——我的意思是,列出并让lines 指向这三只。