11

我正在阅读如何像计算机科学家一样思考,这是“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]行都不是其他行元素的别名。

4

2 回答 2

20

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对象的两个引用。所以元素最终出现在 single 中list。它似乎是重复的,但事实是只有一个list对象,并且对它的引用很多。

相比之下,如果你这样做

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

在这里,您正在创建另一个 list对象+与列表一起使用是一个显式副本),并将对它的引用分配到outerList. 如果您以这种方式“附加”元素(不是真正附加,而是创建另一个列表),innerList将不受影响。

于 2009-06-10T11:27:19.793 回答
-4

列表不是原语,它们是通过引用传递的。列表的副本是指向列表的指针(在 C 行话中)。您对列表所做的任何事情都会发生在列表的所有副本及其内容的副本上,除非您进行浅拷贝。

[[0] * columns] * rows

糟糕,我们刚刚创建了一个指向 [0] 的大列表。改变一个,你就改变了他们。

整数不是通过引用传递的,它们实际上是被复制的,因此 [0] * 内容实际上是在制造大量的新 0 并将它们附加到列表中。

于 2009-06-10T11:11:43.663 回答