编辑:这个问题是关于为什么行为就是这样,而不是如何绕过它,这就是所谓的重复是关于。
我使用以下符号在不同情况下创建特定大小的列表。例如:
>>> [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
>>>
我明显遗漏了一些东西,可能是一个历史事实,或者只是一种不同的方式来观察这里的一致性。
有人可以解释为什么两个不同的代码片段,人们会合理地期望它们彼此等效,实际上最终会隐式地产生不同的和非显而易见的(IMO)结果,特别是考虑到Python'始终显式和明显的?
请注意,我已经知道this question,这与我提出的问题不同。
我只是在寻找详细的解释/理由。如果出现此行为的历史,技术和/或理论原因,请务必提供一两个参考。
答案 0 :(得分:3)
执行以下操作时:
[[]]*n
您首先创建列表,然后将*
运算符与int
n
一起使用。这将获取列表中的任何对象,并创建n次重复。
但是在Python中,显式优于隐式,你不能隐式地复制这些对象。实际上,这与Python的语义一致。
尝试为Python 隐式制作副本的单个案例命名。
此外,它与列表中的添加一致:
l = [1, [], 'a']
l2 = l + l + l
l[1].append('foo')
print(l2)
输出:
[1, ['foo'], 'a', 1, ['foo'], 'a', 1, ['foo'], 'a']
现在,正如评论中所提到的,来自C ++的内容是有道理的,上面的内容会令人惊讶,但如果将其用于Python,那么上面就是期望的。
另一方面:
[[] for _ in range(5)]
列表是否理解。它相当于:
lst = []
for _ in range(5):
lst.append([])
在这里,很明显,每次进入循环时,都会创建一个新列表。这就是字面语法的工作原理。
顺便说一句,除了我喜欢的一个特定习语外,我几乎从不在列表上使用*
运算符:
>>> x = list(range(1, 22))
>>> it_by_three = [iter(x)]*3
>>> for a,b,c in zip(*it_by_three):
... print(a, b, c)
...
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
16 17 18
19 20 21
答案 1 :(得分:1)
对于cpython,源代码的相关部分位于listobject.c中的函数list_repeat
中。下面重复了一个启发性片段,我添加了评论:
np = (PyListObject *) PyList_New(size); // make a new PyListObject
/* some code omitted */
items = np->ob_item; // grabs the list of pointers of the *new* object
if (Py_SIZE(a) == 1) { // this is the case for a 1-element list being multiplied
elem = a->ob_item[0]; // grabs the pointer of the element of the *original* object
for (i = 0; i < n; i++) {
items[i] = elem; // assigns the original pointer to the new list
Py_INCREF(elem);
}
return (PyObject *) np;
}
由于PyListObject
主要是Vector
,其中包含指向列表元素的指针列表,因此将这些点作为元素分配给新PyListObject
很简单。
相反,想象代码是否需要复制位于每个指针的对象。它会更复杂,并且会有明显的性能影响。但是,我不会推测这个设计决定的动机。