Python浅拷贝 深拷贝

作者: ChongmingLiu | 来源:发表于2017-12-28 23:40 被阅读181次

    内存泄漏太可怕。

    Python 可变对象 & 不可变对象

    在Python中,对象分为两种:可变对象不可变对象

    • 不可变对象包括int,float,long,str,tuple等;
    • 可变对象包括list,set,dict等

    需要注意的是:这里说的不可变指的是值的不可变。对于不可变类型的变量,如果要更改变量,则会创建一个新值,把变量绑定到新值上,而旧值如果没有被引用就等待垃圾回收。另外,不可变的类型可以计算hash值,作为字典的key。可变类型数据对对象操作的时候,不需要再在其他地方申请内存,只需要在此对象后面连续申请(+/-)即可,也就是它的内存地址会保持不变,但区域会变长或者变短。
    如下所示:

    list = [1, 2, 3]
    print(id(list))
    arr = [4, 5]
    list = list + arr
    print(id(list))
    
    list2 = [1, 2, 3]
    print(id(list2))
    list2 += [4, 5]
    print(id(list2))
    
    # 输出结果:
    # 4557530312
    # 4557530696
    # 4557530312
    # 4557530312
    
    • list = list + arr,list出现两次,必须执行两次,性能不好,合并必须新建对象list,然后复制两个列表合并,这属于拷贝;
    • list += arr,list只出现一次,因为不生成新对象,因此性能好,只在内存块末尾增加元素。当操作元素为list时,“+=”会自动调用 extend 方法进行合并运算,这属于共享引用.

    Python没有赋值,只有引用

    Python中的“=”传递的是引用(内存地址),应按照C语言中的指针理解。

    Python浅拷贝copy.copy()

    Python中,对于非容器类型(如数字、字符串、和其他原子类型的对象)没有被拷贝一说。

    使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的子对象,依然使用原始的引用,所以原始数据改变,子对象会改变。

    该示例显示了通过传递引用的方式创建obj1,此时obj1 is list

    list = [1, 2, 3, ['a', 'b']]
    obj1 = list
    # 输出list与obj1的地址
    print('id of list =', id(list))
    print('id of obj1 =', id(obj1))
    # obj1原始的值
    print('value of obj1 =', obj1)
    # 修改list的值,看obj1中对应的值是否改变
    list[1] = 999
    print('value of obj1 =', obj1)
    
    # 输出结果:
    # id of list = 4367528520
    # id of obj1 = 4367528520
    # value of obj1 = [1, 2, 3, ['a', 'b']]
    # value of obj1 = [1, 999, 3, ['a', 'b']]
    

    接下来通过浅拷贝创建obj2,可以看到此时list与obj2指向了不同的地址,修改list中的字符串、布尔类型、整数、浮点数、数字不会改变obj2中的值;但是此时obj2的子对象还是与list中的子列表共享一块内存

    obj2 = copy.copy(list)
    # 输出list与obj2的地址
    print('id of list =', id(list))
    print('id of obj2 =', id(obj2))
    # 输出list与obj2中每个元素的地址
    print('id of list = ', [id(it) for it in list])
    print('id of obj2 = ', [id(it) for it in obj2])
    # 修改list[0]的值,看obj2中的值是否改变
    list[0] = -1
    print('value of list =', list)
    print('value of obj2 =', obj2)
    print('-' * 50)
    # 修改list中的子列表,看obj2中的值是否改变
    list[3].append('c')
    print('value of obj2 =', obj2)
    # 输出list与obj2中每个元素的地址
    print('id of list = ', [id(it) for it in list])
    print('id of obj2 = ', [id(it) for it in obj2])
    
    # 输出结果:
    # id of list = 4367528520
    # id of obj2 = 4367558344
    # id of list =  [4345380112, 4346236624, 4345380176, 4367558536]
    # id of obj2 =  [4345380112, 4346236624, 4345380176, 4367558536]
    # value of list = [-1, 999, 3, ['a', 'b']]
    # value of obj2 = [1, 999, 3, ['a', 'b']]
    # --------------------------------------------------
    # value of obj2 = [1, 999, 3, ['a', 'b', 'c']]
    # id of list =  [4345380048, 4346236624, 4345380176, 4367558536]
    # id of obj2 =  [4345380112, 4346236624, 4345380176, 4367558536]
    

    当我们使用下面的操作的时候,会产生浅拷贝的效果:

    • 使用切片[:]操作
    • 使用工厂函数(如list/dir/set)
    • 使用copy模块中的copy()函数

    Python深拷贝copy.deepcopy()

    复制一个容器对象,并且,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝

    list = [1, 2, 3, ['a', 'b']]
    obj3 = copy.deepcopy(list)
    # 输出list与obj3的地址
    print('id of list =', id(list))
    print('id of obj3 =', id(obj3))
    # 输出list与obj3中每个元素的地址
    print('id of list = ', [id(it) for it in list])
    print('id of obj2 = ', [id(it) for it in obj3])
    # 修改list中的子列表,看obj2中的值是否改变
    list[3].append('c')
    # 修改list[0]的值,看obj2中的值是否改变
    print('value of list =', list)
    print('value of obj2 =', obj3)
    
    # 输出结果:
    # id of list = 4370554760
    # id of obj3 = 4370650248
    # id of list =  [4348476688, 4348476720, 4348476752, 4370650440]
    # id of obj2 =  [4348476688, 4348476720, 4348476752, 4370650120]
    # value of list = [1, 2, 3, ['a', 'b', 'c']]
    # value of obj2 = [1, 2, 3, ['a', 'b']]
    

    关于Python内存管理机制请见:
    Python源码阅读-内存管理机制(一)
    Python源码阅读-内存管理机制(二)

    相关文章

      网友评论

        本文标题:Python浅拷贝 深拷贝

        本文链接:https://www.haomeiwen.com/subject/hjnvgxtx.html