为什么在列表上使用乘法运算符会创建指针列表?

xyz*_*man 14 python pointers list

>>> rows = [['']*5]*5
>>> rows
[['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']]
>>> rows[0][0] = 'x'
Run Code Online (Sandbox Code Playgroud)

当然,我希望行成为:

[['x', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']]
Run Code Online (Sandbox Code Playgroud)

相反,我得到:

[['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', '']]
Run Code Online (Sandbox Code Playgroud)

似乎行列表的元素是指向相同的旧['']*5列表的指针.为什么它以这种方式工作,这是一个Python功能?

NPE*_*NPE 17

该行为不是特定于重复运算符(*).例如,如果使用连接两个列表+,行为是相同的:

In [1]: a = [[1]]

In [2]: b = a + a

In [3]: b
Out[3]: [[1], [1]]

In [4]: b[0][0] = 10

In [5]: b
Out[5]: [[10], [10]]
Run Code Online (Sandbox Code Playgroud)

这与列表是对象,对象通过引用存储的事实有关.当你使用*et al时,它是重复的引用,因此你看到的行为.

以下演示了所有元素rows具有相同的标识(即CPython中的内存地址):

In [6]: rows = [['']*5]*5

In [7]: for row in rows:
   ...:     print id(row)
   ...:     
   ...:     
15975992
15975992
15975992
15975992
15975992
Run Code Online (Sandbox Code Playgroud)

以下等同于您的示例,除了它为行创建五个不同的列表:

rows = [['']*5 for i in range(5)]
Run Code Online (Sandbox Code Playgroud)


Sve*_*ach 5

名称,函数参数和容器具有引用语义的事实是Python中非常基本的设计决策。它会影响Python在许多方面的工作方式,而您只是选择了这些方面之一。在许多情况下,引用语义更方便,而在其他情况下,副本会更方便。在Python中,您始终可以根据需要显式创建一个副本,或者在这种情况下,请使用列表推导:

rows = [[''] * 5 for i in range(5)]
Run Code Online (Sandbox Code Playgroud)

您可以设计一种具有不同语义的编程语言,并且有许多语言确实具有不同的语义,以及具有相似语义的语言。为什么要做出这个决定有点难以回答-语言只需要具有一些语义,您就可以随时问为什么。您还可以问为什么要动态键入Python,最后的答案是,这只是Guido于1989年决定的。


Jim*_*lay 5

您是正确的,Python 在“幕后”使用指针,是的,这是一个功能。我不确定他们为什么这样做 - 我认为这是为了速度和减少内存使用。

顺便说一下,这个问题就是为什么理解浅拷贝和深拷贝之间的区别很重要。