问题 乘法运算符应用于列表(数据结构)


我正在阅读 如何像计算机科学家一样思考 这是“Python编程”的介绍性文本。

我想澄清乘法运算符的行为(*)当应用于列表时。

考虑这个功能 make_matrix

def make_matrix(rows, columns):
"""
  >>> make_matrix(4, 2)
  [[0, 0], [0, 0], [0, 0], [0, 0]]
  >>> m = make_matrix(4, 2)
  >>> m[1][1] = 7
  >>> m
  [[0, 0], [0, 7], [0, 0], [0, 0]]
"""
return [[0] * columns] * rows

实际输出是

[[0, 7], [0, 7], [0, 7], [0, 7]]

正确的版本 make_matrix  是:

def make_matrix(rows, columns):
"""
  >>> make_matrix(3, 5)
  [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
  >>> make_matrix(4, 2)
  [[0, 0], [0, 0], [0, 0], [0, 0]]
  >>> m = make_matrix(4, 2)
  >>> m[1][1] = 7
  >>> m
  [[0, 0], [0, 7], [0, 0], [0, 0]]
"""
matrix = []
for row in range(rows):
    matrix += [[0] * columns]
return matrix

第一版的原因 make_matrix 失败(如9.8中的书中所解释的那样)

...每一行都是其他行的别名......

我想知道为什么

[[0] * columns] * rows

原因 ...每一行都是其他行的别名......

但不是

[[0] * columns]

即为什么每一个 [0] 连续不是其他行元素的别名。


6090
2018-06-10 11:06


起源



答案:


python中的所有内容都是对象,除非明确要求,否则python永远不会复制。

当你这样做

innerList = [0] * 10

你创建一个包含10个元素的列表, 所有这些都指的是同一个 int 目的 0

因为整数对象是 一成不变,当你这样做

innerList[1] = 15

您正在更改列表的第二个元素,以便它引用另一个整数 15。这总是有效的,因为 int 物体不变性。

这就是为什么

outerList = innerList * 5

会创造一个 list 具有5个元素的对象,每个元素都是一个引用 一样 innerList 如上所述。但是由于 list 对象是 易变的

outerList[2].append('something')

是相同的:

innerList.append('something')

因为它们是两个引用的 相同 list 目的。所以元素最终会成为那个元素 list。它似乎是重复的,但事实是只有一个 list 对象,以及对它的许多引用。

相反,如果你这样做

outerList[1] = outerList[1] + ['something']

这个给你 创建 另一个  list 目的 (使用 + with lists是一个显式副本),并将对它的引用分配到第二个位置 outerList。如果你以这种方式“追加”元素(不是真正附加,而是创建另一个列表), innerList 将不受影响。


18
2018-06-10 11:27



听起来很完美,非常感谢!! - fizzbuzz
+1 - 非常好的解释 - Luiz Damim


答案:


python中的所有内容都是对象,除非明确要求,否则python永远不会复制。

当你这样做

innerList = [0] * 10

你创建一个包含10个元素的列表, 所有这些都指的是同一个 int 目的 0

因为整数对象是 一成不变,当你这样做

innerList[1] = 15

您正在更改列表的第二个元素,以便它引用另一个整数 15。这总是有效的,因为 int 物体不变性。

这就是为什么

outerList = innerList * 5

会创造一个 list 具有5个元素的对象,每个元素都是一个引用 一样 innerList 如上所述。但是由于 list 对象是 易变的

outerList[2].append('something')

是相同的:

innerList.append('something')

因为它们是两个引用的 相同 list 目的。所以元素最终会成为那个元素 list。它似乎是重复的,但事实是只有一个 list 对象,以及对它的许多引用。

相反,如果你这样做

outerList[1] = outerList[1] + ['something']

这个给你 创建 另一个  list 目的 (使用 + with lists是一个显式副本),并将对它的引用分配到第二个位置 outerList。如果你以这种方式“追加”元素(不是真正附加,而是创建另一个列表), innerList 将不受影响。


18
2018-06-10 11:27



听起来很完美,非常感谢!! - fizzbuzz
+1 - 非常好的解释 - Luiz Damim


列表不是基元,它们通过引用传递。列表的副本是指向列表的指针(用C术语表示)。除非您执行浅拷贝,否则您对列表所做的任何操作都会发生在列表的所有副本及其内容的副本中。

[[0] * columns] * rows

糟糕,我们刚刚列出了一个指向[0]的指针。改变一个你就改变它们。

整数不是通过引用传递的,它们是真正被复制的,因此[0] *内容实际上会产生很多新的0并将它们附加到列表中。


-3
2018-06-10 11:11



aha,对于大小为1的列表,这看起来不像是一种特殊的行为。我听说“Pythonista”不喜欢特殊情况(如禅宗中所解释的那样“......特殊情况不够特别打破规则......“)。 - fizzbuzz
误导。 python中没有“原始”这样的东西。 EverythingTHING是一个对象,并且一直通过引用传递,包括INTEGERS。实际上变量只是命名引用。这里的问题是列表是可变的,而int不是。 - nosklo
例如:a = 5; b = a; print a is b#返回True,因为a和b是对同一对象的引用。 - nosklo
-1,整数通过引用传递。 - Kiv