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

11 投票
2 回答
4990 浏览
提问于 2025-04-15 12:09

我正在阅读《如何像计算机科学家一样思考》,这是一本关于“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 个回答

-4

列表不是基本数据类型,它们是通过引用传递的。简单来说,列表的副本其实是指向原列表的一个指针(用C语言的说法)。你对这个列表做的任何操作,都会影响到所有的副本和它里面的内容,除非你做的是浅拷贝。

[[0] * columns] * rows

哎呀,我们刚刚创建了一个指向[0]的大列表。你改变其中一个,其他的也都会跟着改变。

整数不是通过引用传递的,它们是真正被复制的,所以说[0] * 内容实际上是在创建很多新的0,并把它们添加到列表里。

20

在Python中,所有东西都是对象,而且Python不会随便复制东西,除非你特别要求。

当你执行

innerList = [0] * 10

时,你创建了一个包含10个元素的列表,所有这些元素都指向同一个整数对象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将不会受到影响。

撰写回答