为什么创建列表列表会产生意外行为?

cod*_*edd 1 python list python-3.x

编辑:这个问题是关于为什么这种行为是这样的,而不是如何绕过它,这就是所谓的重复的内容。


我使用以下符号在不同情况下创建特定大小的列表。例如:

>>> [None] * 5
[None, None, None, None, None]
>>>
Run Code Online (Sandbox Code Playgroud)

这似乎按预期工作并且短于:

>>> [None for _ in range(5)]
[None, None, None, None, None]
>>>
Run Code Online (Sandbox Code Playgroud)

然后我尝试使用相同的方法创建列表列表:

>>> [[]] * 5
[[], [], [], [], []]
>>>
Run Code Online (Sandbox Code Playgroud)

很公平。它似乎按预期工作。

然而,在检查调试器时,我注意到所有子列表存储桶都具有相同的值,即使我只添加了一个项目。例如:

>>> t = [[]] * 5
>>> t
[[], [], [], [], []]
>>> t[1].append(4)
>>> t
[[4], [4], [4], [4], [4]]
>>> t[0] is t[1]
True
>>>
Run Code Online (Sandbox Code Playgroud)

我并不期望所有顶级数组元素都是对单个子列表的引用;我期望有 5 个独立的子列表。

为此,我必须编写如下代码:

>>> t = [[] for _ in range(5)]
>>> t
[[], [], [], [], []]
>>> t[2].append(4)
>>> t
[[], [], [4], [], []]
>>> t[0] is t[1]
False
>>>
Run Code Online (Sandbox Code Playgroud)

我显然遗漏了一些东西,可能是历史事实,或者只是看待这里一致性的不同方式。

有人可以解释为什么两个不同的代码片段,人们合理地期望彼此等效,实际上最终隐式地产生不同的和不明显的(IMO)结果,特别是考虑到Python总是显明显的禅宗?

请注意,我已经知道这个问题,这与我要问的不同。

我只是在寻找详细的解释/理由。如果此行为存在历史、技术和/或理论原因,请务必包含一两个参考资料。

jua*_*aga 5

当您执行以下操作时:

[[]]*n
Run Code Online (Sandbox Code Playgroud)

首先创建一个列表,然后使用*带有 的运算符int n。这会获取列表中的任何对象,并创建它的 n 多次重复。

但由于在 Python 中,显式优于隐式,因此您不会隐式地复制这些对象。确实,这符合Python的语义。

尝试举出 Python隐式创建副本的单个案例。

此外,它与列表中添加的内容一致:

l = [1, [], 'a']

l2 = l + l + l

l[1].append('foo')

print(l2)
Run Code Online (Sandbox Code Playgroud)

和输出:

[1, ['foo'], 'a', 1, ['foo'], 'a', 1, ['foo'], 'a']
Run Code Online (Sandbox Code Playgroud)

现在,正如评论中所指出的,对于来自 C++ 的人来说,上面的内容令人惊讶是有道理的,但如果一个人习惯了 Python,那么上面的内容就是人们所期望

另一方面:

[[] for _ in range(5)]
Run Code Online (Sandbox Code Playgroud)

是一个列表理解。它相当于:

lst = []
for _ in range(5):
    lst.append([])
Run Code Online (Sandbox Code Playgroud)

很明显,每次进入循环时都会创建一个新列表。这就是字面语法的工作原理。

顺便说一句,我几乎从不*在列表上使用运算符,除了我喜欢的一个特定习惯用法:

>>> 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
Run Code Online (Sandbox Code Playgroud)

  • 既然您试图解释_为什么_,我认为详细了解如何实现“list.__mul__”,以及为什么没有为其他可变结构(如集合和列表)实现它背后的原因应该是一个好主意。 (3认同)