>>> rows = [['']*5]*5
>>> rows
[['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']]
>>> rows[0][0] = 'x'
当然,我希望行成为:
[['x', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']]
相反,我得到:
[['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', '']]
似乎行列表的元素是指向同一个旧[''] * 5列表的指针。为什么它以这种方式工作,这是一个Python功能吗?
答案 0 :(得分: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]]
这与列表是对象,对象通过引用存储的事实有关。当你使用*
等时,它是重复的引用,因此你看到的行为。
以下表明rows
的所有元素具有相同的标识(即CPython中的内存地址):
In [6]: rows = [['']*5]*5
In [7]: for row in rows:
...: print id(row)
...:
...:
15975992
15975992
15975992
15975992
15975992
以下内容与您的示例等效,只是它为行创建了五个不同的列表:
rows = [['']*5 for i in range(5)]
答案 1 :(得分:4)
名称,函数参数和容器具有引用语义这一事实是Python中一个非常基本的设计决策。它影响了Python在许多方面的工作方式,您只选择了其中一个方面。在许多情况下,引用语义更方便,而在其他情况下,副本会更方便。在Python中,您可以随时显式创建副本,或者在这种情况下,使用列表推导:
rows = [[''] * 5 for i in range(5)]
您可以设计一种具有不同语义的编程语言,并且有许多语言具有不同的语义,以及具有相似语义的语言。为什么做出这个决定有点难以回答 - 一种语言只需要有一些语义,你总能问为什么。你也可以问为什么Python是动态类型的,最后答案是这就是Guido在1989年决定的。
答案 2 :(得分:4)
你是正确的,Python正在“引擎盖下”使用指针,是的,这是一个功能。我不确定为什么他们这样做 - 我认为这是为了速度和减少内存使用。
顺便说一下,这个问题是为什么理解shallow copies and deep copies之间的区别至关重要。