• Python 深浅拷贝 (Shallow copy and Deep copy in Python)


    前言

    昨天刷公众号看到一篇描述py优雅语法的文章,心痒之下到家就开始尝试,学习了for else statement,yield和py版三目写法。在列表切片这部分中,对作者的列表拷贝写法,有些不太理解。

    # 拷贝
    copy_items = items[::] 或者 items[:]

    尝试

    首先开一个python,随便建一个列表
    l=[1,2,3]
    将其进行两种方法的拷贝:

    1. 我的写法
      c=l
    2. 作者的写法
      d=l[:]

    分别打印了cd,并没有什么差别,仔细斟酌了一下作者的用意,觉得应该有一些深层次的考虑。
    于是使用id()分别查看两边的内存地址,这一打印出现了不同的结果。

    >>> id(l)
    39179656L
    >>> id(c)
    39179656L
    >>> id(d)
    39179272L

    使用直接赋值的c的内存地址和原列表l的内存地址是一样的,而用切片方法拷贝的d的内存地址不一样。
    我尝试改动l的值看一下结果。

    >>> l.append('z')
    >>> l
    [1, 2, 3, 'z']
    >>> c
    [1, 2, 3, 'z']
    >>> d
    [1, 2, 3]

    此时区别就显示出来了,使用直接赋值的c因为和原列表l指向同一个内存地址,所以当修改l的值的时候,打印c也发现同样的改变。

    探索

    稍微搜一下怎么拷贝一个列表,发现这个是有专有名词的,叫做 “深浅拷贝” (copy,deepcopy)。我原来直接赋值的写法只是将内存地址的引用传递到一个新的对象里,连浅拷贝都算不上。
    Python的拷贝有一个专门的模块,叫做copy

    浅拷贝

    import copy;
    >>> l=[1,2,3,[4,5],6]
    >>> c=copy.copy(l)
    >>> id(l)
    39195912L
    >>> id(c)
    39238600L

    从内存引用里清晰的显示,至少内存地址不一样了,对l进行内容变更应该不会影响到c

    >>> l.append('z')
    >>> l
    [1, 2, 3, [4, 5], 6, 'z']
    >>> c
    [1, 2, 3, [4, 5], 6]

    但是毕竟是浅拷贝,只是拷贝了最外层对象,没有拷贝子对象。

    >>> id(l[3])
    39195784L
    >>> id(c[3])
    39195784L
    >>> l[3].append('az')
    >>> l
    [1, 2, 3, [4, 5, 'az'], 6, 'z']
    >>> c
    [1, 2, 3, [4, 5, 'az'], 6]

    果然原列表l的子对象的内存地址和浅拷贝c的对应子对象的内存地址一样,所以当原列表的子对象内容发生改变时,也会影响到c

    深拷贝

    有了浅拷贝的经验,直接造一个深拷贝对象d,先查看一下外层对象和子对象的内存地址。

    >>> l=[1,2,3,[4,5],6]
    >>> d=copy.deepcopy(l)
    >>> id(l)
    39236296L
    >>> id(d)
    39195912L
    >>> id(l[3])
    39179656L
    >>> id(d[3])
    39236040L

    结果清晰的显示,原列表l和深拷贝对象d对应外层对象和子对象的内存地址均不同。

    >>> l.append('z')
    >>> l
    [1, 2, 3, [4, 5], 6, 'z']
    >>> d
    [1, 2, 3, [4, 5], 6]
    >>> l[3].append('az')
    >>> l
    [1, 2, 3, [4, 5, 'az'], 6, 'z']
    >>> d
    [1, 2, 3, [4, 5], 6]

    再查看一下结果,验证了我的猜想。

    总结

    Python是一门脚本语言,声明一个对象实际在内存中创建了一个地址存放对象,将对象名指向那个内存地址。用PHP的赋值方法进行赋值时,只是创建了一个新的对象名同时指向同一个内存地址。
    py优雅语法的作者所用的列表拷贝方法c=l[:]用的就是浅拷贝,只是写法相对于copy.copy()更简洁。
    通过Copy模块的代码可以发现deepcopy是在copy的基础上执行了递归。

    # C:Python27Libcopy.py
    def deepcopy(x, memo=None, _nil=[]):
    ...
    y = _reconstruct(x, rv, 1, memo);
    ...
    def _reconstruct(x, info, deep, memo=None):
    ...
    if deep:
    args = deepcopy(args, memo);
    ...

    备注:本文发布于2017-08-03,我github page的原文地址

  • 相关阅读:
    数据结构与算法(C++)之swap交换
    常用shell脚本
    Java知识库
    jenkins:你们是怎么在控制台实时打印服务启动日志的?
    mqtt压力测试工具emqtt
    postman中获取环境变量和全局变量
    不想当将军的士兵不是好士兵吗?
    永久关闭火狐浏览器自动更新的方法
    性能测试案例:线程池拒绝策略使用不当导致并发一上去就会出现大量报错
    性能测试案例:一个频繁fgc问题
  • 原文地址:https://www.cnblogs.com/waltersgarden/p/7991733.html
Copyright © 2020-2023  润新知