乘法运算符应用于列表(数据结构)
我正在阅读《如何像计算机科学家一样思考》,这是一本关于“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 个回答
列表不是基本数据类型,它们是通过引用传递的。简单来说,列表的副本其实是指向原列表的一个指针(用C语言的说法)。你对这个列表做的任何操作,都会影响到所有的副本和它里面的内容,除非你做的是浅拷贝。
[[0] * columns] * rows
哎呀,我们刚刚创建了一个指向[0]的大列表。你改变其中一个,其他的也都会跟着改变。
整数不是通过引用传递的,它们是真正被复制的,所以说[0] * 内容实际上是在创建很多新的0,并把它们添加到列表里。
在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
将不会受到影响。