【发布时间】:2017-07-04 23:54:56
【问题描述】:
编辑:这个问题是关于为什么行为是什么,而不是如何绕过它,这就是所谓的重复是关于。
我在不同情况下使用了以下符号来创建一定大小的列表。例如:
>>> [None] * 5
[None, None, None, None, None]
>>>
这似乎按预期工作,并且比:
>>> [None for _ in range(5)]
[None, None, None, None, None]
>>>
然后我尝试使用相同的方法创建一个列表列表:
>>> [[]] * 5
[[], [], [], [], []]
>>>
很公平。它似乎按预期工作。
但是,在通过调试器时,我注意到 所有 子列表存储桶具有相同的值,即使我只添加了一个 单个 项。例如:
>>> t = [[]] * 5
>>> t
[[], [], [], [], []]
>>> t[1].append(4)
>>> t
[[4], [4], [4], [4], [4]]
>>> t[0] is t[1]
True
>>>
我没想到所有顶级数组元素都是对单个子列表的引用;我预计有 5 个独立子列表。
为此,我不得不编写如下代码:
>>> t = [[] for _ in range(5)]
>>> t
[[], [], [], [], []]
>>> t[2].append(4)
>>> t
[[], [], [4], [], []]
>>> t[0] is t[1]
False
>>>
我显然遗漏了一些东西,可能是一个历史事实,或者只是一种不同的方式来看待这里的一致性。
谁能解释一下为什么两个不同的代码 sn-ps 可以合理地期望彼此等效,实际上最终会隐式产生不同且不明显 (IMO) 的结果,尤其是考虑到 Python 的 zen总是明确和明显?
请注意,我已经知道this question,这与我所要求的不同。
我只是在寻找详细的解释/理由。如果此行为存在历史、技术和/或理论原因,请务必提供一两个参考资料。
【问题讨论】:
-
这肯定有答案....似乎找不到。但这在很多地方都有很好的解释。
-
@idjaw 好吧,老实说,我被这个撕裂了。我最初将它作为那个著名的目标的欺骗目标,但在进一步考虑后重新打开它。欺骗目标问“有人可以解释发生了什么,以及如何绕过它吗?”但是 OP 已经知道发生了什么,以及如何绕过它。他们的问题是为什么这两种方法在语义上不等效。
-
“尤其是考虑到 Python 的禅意总是明确而明显”——好吧,问题在于,就 Python 而言,你明确地知道 Python 应该做什么.你只是不明白你告诉它做什么。虽然他们本可以让事情变得更加明确,但将新手可能希望制作副本的所有内容拆分为单独的副本和无副本版本,但一直显式编写
x nc= [1, 2, 3]或y nc= [[None] nc* 4] c* 5会变得非常麻烦,而且这将是一个相当大的设计改变,让复制变得如此重要。 -
[[]] * 4只是看到它乘以 4 的对象是一个 1 元素列表。它不知道如何构建通用列表元素的副本,它无法重新评估[]表达式,因为它看不到该表达式,并且特殊类型的列表元素以制作它们的副本会不一致。在没有不一致或大量语言重新设计的情况下,*的唯一选择是复制引用而不是对象。 -
@ray 我还注意到您一直在使用术语“数组”。但这些不是数组。它们是列表。是的,在内部深处有一个 Py_Object 指针的 C 数组,但这是一个实现细节。它们是比单纯的数组更庞大的数据结构。它们是具有摊销常数时间
.append和常数时间索引的异构、可调整大小的列表...
标签: python list python-3.x