2007-08-28

 

Python Cookbook 4.5 创建多维列表,避免元素共享引用

需求:

你需要简历多维列表,并且要避免元素引用共享.

讨论:

为了创建列表且避免引用共享,使用列表递推式,比如,建立一个5*10的数组,并初始化为0:

multilist = [[0 for col in range(5)] for row in range(10)]

当新接触Python时,对它的用列表乘法来初始化数组很兴奋,那真是一个聪明的语法:

>>> alist = [0] * 5

这样来获得包含5个元素的数组很方便.
很自然,也会想到2维数组可以这样写:

>>> multi = [[0] * 5] * 3
>>> print multi
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

这样写看起来有效,但是很快新手们会遇到问题:

>>> multi[0][0] = 'oops!'
>>> print multi
[['oops!', 0, 0, 0, 0], ['oops!', 0, 0, 0, 0], ['oops!', 0, 0, 0, 0]]

这个问题曾经困扰了很多人, 尤其是新手.为了说明问题,我们最好首先把它分解成两部分:

>>> row = [0] * 5 # a list with five references to 0
>>> multi = [row] * 3 # a list with three references to the row object

这个分解说明了问题,它和前面的代码功能是一样的,如果你改变了multi[0][0]的值,也会改变multi[1][0].
上面代码后面的注释说明问题,用列表和数字相乘创建新的列表,并且拥有以前列表元素的内容.在这种情况下,创建了row,我们不用关心引用是否被复制,因为引用的是一个数字, 也可以推广到不可修改对象.也就是说,如果引用的对象是一个不可修改对象,那么该对象和该对象的引用就没有什么实质的区别.在第二行 ,我们从row又创建了一个列表,它的里面包含的都是row的应用.因此,multi包含了3个row对象的引用.所以,当row的一个元素修改后,其余的也会表现出来.
而列表递推式,就像本节开始给出的代码,避免了这个问题.在进行列表递推式的时候,处于内层的递推式不会进行引用共享.如果你想更深入的讨论这个问题,我们甚至可以说你连内层的递推式也不需要.换句话说,如果我问下面这个语句是不是等价:

multilist = [[0]*5 for row in range(10)]

那么答案是yes.事实上,使用这种方法,比使用嵌套递推式快两倍,那么为什么我们不推荐使用后面这种方法呢?因为提升速度的绝对值太小了,对你的程序性能影响并不大,而且重要的是,它牺牲了可读性!

标签:


Comments: 发表评论



<< Home

This page is powered by Blogger. Isn't yours?