首页 > 代码库 > python list的深拷贝与浅拷贝-以及初始化空白list的方法(1)

python list的深拷贝与浅拷贝-以及初始化空白list的方法(1)

 

在一次做题的时候遇到了一件令人非常匪夷所思的“bug”:我想要做的事情是,初始化shape确定,但值为“空”的list,并且是一个二维的list,开始我是这么做的:

l1=[[""]*3]*3
print l1
[[‘‘, ‘‘, ‘‘], [‘‘, ‘‘, ‘‘], [‘‘, ‘‘, ‘‘]]

可以看到这个矩阵的shape,可以理解成一个N*N的矩阵。

把题目也贴出来吧——题目要求很简单,输入一个N*N的矩阵,输出这个矩阵的顺时针旋转90°之后的矩阵。一开始我的代码如下:

class Rotate:
    def rotateMatrix(self, mat, n):
        # write code here
        v=[[""]*3]*3
        for i in range(n):
            for j in range(n):
                v[i][j]=mat[n-j-1][i]
        return v

输入的测试数据为:

[[1,2,3],[4,5,6],[7,8,9]]

正确的输出应该为:

[[7,4,1],[8,5,2],[9,6,3]]

而我的运行结果却是:

[[9,6,3],[9,6,3],[9,6,3]]

非常不解,输出的三个子数组居然是一样的结果

后来对原来的代码略加修改,把“空”list的初始化定义为:v=[[0 for i in range(n)] for j in range(n)],即:

class Rotate:
    def rotateMatrix(self, mat, n):
        # write code here
        v=[[0 for i in range(n)] for j in range(n)]
        for i in range(n):
            for j in range(n):
                v[i][j]=mat[n-j-1][i]
        return v

此时终于输出了正确的结果[[9,6,3],[9,6,3],[9,6,3]],代码测试通过。

 

这里对比一下这两种初始化方法:

技术分享

至少在输出结果看,长的是一模一样,难道这里的l1和l2真的没有区别吗?这里再介绍一个常用函数------id(x),它表示括号内变量所在的内存地址

我们分别来看一下l1,l2内子list的内存地址情况:

技术分享

震惊!这里可以看到,l1的三个子list的内存地址居然一模一样!而l2的子list所在的内存地址各不相同!??

我们队l1,l2的子list分别进行extend操作,如下:

技术分享

我们本来只是对l1/l2的第二个子list进行extend操作,但结果却是l1的三个子数组都受到了影响,而l2是只有第二个子数组实现了extend操作。

再分别看此时各子list的内存地址:

技术分享

与刚才的结果一致,l1的三个子数组位于同一段内存地址,l2则各不相同。

用个直接使用“=”对l1的子list进行赋值呢?

技术分享

此时,只有被赋值的l1[1]成功赋值,另外两个没有变,并且l1[1]的内存地址已经发生变化,而l1[0],l1[2]依然不变

至此可以总结一下原因了:

1.使用*进行list“复制”,其生成的新数组与被复制的数组其实是在同一段内存地址当中,这样的复制方式成为潜复制

2.浅复制进行初始化的结果就是,在对复制之后的对象进行相关操作时,被复制的对象会受到同样的影响,因为他们本质是同一段list,均位于相同的地址

3.使用“=”进行赋值,是将"="后的内容创建到一个新的内存地址当中,并将"="左的变量的地址重新指向"="右产生的新地址当中

具体到解决实际问题,在初始化一个"已知shape,但内部的值待填充"的一个"空"list时,建议使用for循环进行初始化,例如上文提到这个格式,即:

v=[[‘‘ for i in range(m)] for j in range(n)]

 

python list的深拷贝与浅拷贝-以及初始化空白list的方法(1)