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)
名称,函数参数和容器具有引用语义的事实是Python中非常基本的设计决策。它会影响Python在许多方面的工作方式,而您只是选择了这些方面之一。在许多情况下,引用语义更方便,而在其他情况下,副本会更方便。在Python中,您始终可以根据需要显式创建一个副本,或者在这种情况下,请使用列表推导:
rows = [[''] * 5 for i in range(5)]
Run Code Online (Sandbox Code Playgroud)
您可以设计一种具有不同语义的编程语言,并且有许多语言确实具有不同的语义,以及具有相似语义的语言。为什么要做出这个决定有点难以回答-语言只需要具有一些语义,您就可以随时问为什么。您还可以问为什么要动态键入Python,最后的答案是,这只是Guido于1989年决定的。