2009-06-10 74 views
11

我在阅读How to think like a computer scientist这是“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]不是其他行元素的别名。

回答

18

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将不受影响。

-3

列表不是原语,它们通过引用传递。列表的副本是指向列表的指针(用C术语表示)。你对列表所做的任何事情都会发生在列表的所有副本和其内容的副本上,除非你做一个浅拷贝。

[[0] * columns] * rows 

糟糕,我们刚刚制作了一大串指向[0]的指针。改变一个,你改变它们。

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

+0

aha,难道这不像一个大小为1的特殊行为类型。 我听说“Pythonista”不喜欢特殊情况(正如Python中的Zen所解释的那样)...特殊情况下aren没有足够的特殊性来打破规则......“)。 – fizzbuzz 2009-06-10 11:23:48

+4

误导。 python中没有“原始”这样的东西。一切都是一个对象,并且始终通过引用传递,包括INTEGERS。事实上,变量只是名称引用。这里的问题是列表是可变的,而整数不是。 – nosklo 2009-06-10 11:32:03