• python中的深拷贝和浅拷贝问题


    python中的深拷贝和浅拷贝问题

    平时写Python用惯了numpy的矩阵类型,只用python自带的list做有关矩阵的(二维数组的)处理的时候碰到各种bug。这里是今日份的bug和解决方案。

    问题缘起

    在一个程序中,我们希望用list实现一个二维数组,然后对其中的元素挨个根据下标的指引来进行赋值。我们对这个二维数组也就是矩阵的初始化是这样的:

    m, n = 5, 3
    matrix = [[1] * n] * m

    其中m,n分别是行数和列数。乍一看没有什么问题,但是在赋值的时候出现了这样的一幕:

    matrix
    Out[199]: [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
    
    matrix[1][0] = 233
    
    matrix
    Out[201]: [[233, 1, 1], [233, 1, 1], [233, 1, 1], [233, 1, 1], [233, 1, 1]]

    ???
    我们发现,虽然用的是二维数组的下标,结果不仅仅我们的第1行第0列被赋值,而且所有其他行的第0列也跟着赋值了。这是什么原因呢?

    问题就处在我们的初始化的方法上。

    深拷贝和浅拷贝

    在python 中,对一个list后面用 乘号 再加上 数字 的方法来初始化一个list,实际上是对这个list进行了浅拷贝(shallow copy),在python中,有深拷贝(deep copy) 和 浅拷贝 的区别。简单来讲就是:深拷贝就是把要拷贝的对象整体复制一份,存在新开辟的空间里;而浅拷贝指的是,对于要拷贝的对象要复制一份,但是对于其内部的子对象就不复制了,而是直接引用,也就是类似于添加了一个链接而已,如下:

    import copy
    a
    Out[210]: [888, 2, 3, [4, 5]]
    b = copy.copy(a)
    b
    Out[212]: [888, 2, 3, [4, 5]]
    b[0] = 233
    b
    Out[214]: [233, 2, 3, [4, 5]]
    a
    Out[215]: [888, 2, 3, [4, 5]]
    b[3][1] = 666
    b
    Out[217]: [233, 2, 3, [4, 666]]
    a
    Out[218]: [888, 2, 3, [4, 666]]

    这里就很明显了,我们对a做一个浅拷贝,目的是b,然后我们对b进行操作,如果对list中的整数赋值,也就是对象中的元素赋值,那么就只改变b中的这个位置的元素值,而a的不变;但是如果我们对list中的list,也就是子对象进行赋值,那么我们法线,这个操作同样也影响了a中的结果。

    copy 这个模块里的copy()是浅拷贝的函数,我们在试一下深拷贝deepcopy()函数:

    a
    Out[219]: [888, 2, 3, [4, 666]]
    b = copy.deepcopy(a)
    b
    Out[221]: [888, 2, 3, [4, 666]]
    b[0] = 1
    b
    Out[223]: [1, 2, 3, [4, 666]]
    a
    Out[224]: [888, 2, 3, [4, 666]]
    b[3][0] = 233
    b
    Out[226]: [1, 2, 3, [233, 666]]
    a
    Out[227]: [888, 2, 3, [4, 666]]

    这就很科学了!两个对象 a 和 b 互不打扰,很和谐。

    另外,我们通常的赋值操作(list的直接赋值)又是怎样的结果呢?

    a
    Out[229]: [888, 2, 3, [4, 666]]
    b = a
    b
    Out[231]: [888, 2, 3, [4, 666]]
    b[0] = 1
    b
    Out[233]: [1, 2, 3, [4, 666]]
    a
    Out[234]: [1, 2, 3, [4, 666]]
    b[3][0] = 1
    b
    Out[236]: [1, 2, 3, [1, 666]]
    a
    Out[237]: [1, 2, 3, [1, 666]]

    可以看出来,直接赋值的结果是比浅拷贝还浅拷贝的。。。因为a和b根本上就是指向的同一个对象!也就是说,a和b是这个对象的两个引用(reference),修改一个就会改变另一个。

    对上文中问题的解释

    了解了这两个概念的区别,就可以解释上述的问题。首先,我们对

    [1] * 3 # 创建了 1 的三个浅拷贝,得到了[1,1,1],此时我们修改某个1,不会影响其他的,因为这是int的元素,不是子对象
    [[1] * 3] * 4 创建了list列表[1,1,1]的四个浅拷贝,这里得到的list是以list为元素类型的,因此改变一个就会影响其他
    # 这就是我们开始会得到所有行都改变的原因,因为他们实际上指向的是同一个东西!

    解决方法

    改用如下方式初始化数组,就可以得到一个可以通过二维下标访问的矩阵了。

    matrix = [[ 1 for i in range(n)] for i in range(m)]

    这就是之前整理过的list comprehension的方法生成list。

    (貌似这篇文章里有好多感叹号。。。因为学艺不精。。。被这个bug折腾了半天才发现它。。。好气。。。)

    2018年03月30日20:07:40

  • 相关阅读:
    再谈用java实现Smtp发送邮件之Socket编程
    Android TextView设置个别字体样式
    Spring4.0MVC学习资料,注解自己主动扫描bean,自己主动注入bean(二)
    Angular团队公布路线图,并演示怎样与React Native集成
    [LeetCode]Remove Element
    poj2481 Cows
    Spark SQL 源代码分析之Physical Plan 到 RDD的详细实现
    MySQL5.6 怎样优化慢查询的SQL语句 -- 慢日志介绍
    容器使用笔记(List篇)
    【Java编程】建立一个简单的JDBC连接-Drivers, Connection, Statement and PreparedStatement
  • 原文地址:https://www.cnblogs.com/morikokyuro/p/13256757.html
Copyright © 2020-2023  润新知