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

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]不是其他行元素的别名。

2个回答

20

Python中的所有东西都是对象,除非明确要求,否则Python永远不会进行复制。

当你执行

innerList = [0] * 10

你创建了一个包含10个元素的列表,所有元素都指向同一个 int 对象0

由于整数对象是不可变的,所以当你执行

innerList[1] = 15

你正在更改列表的第二个元素,使其指向另一个整数15。这总是有效的,因为int对象是不可变的。

这就是为什么。

outerList = innerList * 5

将创建一个包含5个元素的list对象,每个元素都是对与上述相同的innerList的引用。但由于list对象是可变的

outerList[2].append('something')

与以下代码等价:

innerList.append('something')
因为它们都是指向同一列表对象list的引用。因此元素最终会出现在这个单独的list中。看起来它被复制了,但事实上只有一个list对象,同时有多个指向它的引用。
相比之下,如果你执行以下操作:
outerList[1] = outerList[1] + ['something']

在这里,你正在创建另一个list对象(使用列表的+是显式复制),并将其引用分配到outerList的第二个位置。如果以这种方式"附加"元素(并不是真正地附加,而是创建另一个列表),则innerList不会受到影响。


这只是告诉人们为什么错误的事情是错误的,它并没有提供一个好的解决方案。解决方案:outerList = [[0]*10 for _ in range(5)] 是Pythonic的,紧凑的,并且不会生成浅拷贝。 - smci

-4

列表不是原始类型,它们是通过引用传递的。列表的副本是指向列表的指针(在 C 术语中)。除非进行浅复制,否则对列表所做的任何操作都会影响所有列表的副本以及其内容的副本。

[[0] * columns] * rows

糟糕,我们刚刚创建了一个指向[0]的大列表。更改其中一个,所有的都会被更改。

整数不是通过引用传递的,它们实际上是被复制的,因此[0] * contents实际上会创建许多新的0并将它们附加到列表中。


啊哈,这似乎是一个大小为1的列表所特有的一种行为。 我听说“Pythonista”不喜欢特殊情况(正如Python之禅所述:“......特殊情况并不足以打破规则......”)。 - Aman Aggarwal
4
有误导成分。在Python中没有所谓的“原始类型”。每个东西都是一个对象,一直通过引用传递,包括整数。事实上,变量只是被命名的引用。问题在于列表是可变的,而整数不是。 - nosklo
例子:a = 5; b = a; print a is b # 这将返回True,因为a和b是指向同一对象的引用。 - nosklo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接