Python中的深复制与浅复制机制详解

基于Python对深复制和浅复制的理解

  • 一、准备
  • (一)对象
  • 1. 对象的特性
  • 2. 常见的Python内置类型
  • 3. 可变对象与不可变对象
  • (二)变量与对象
  • 1. 可变对象与不可变对象的创建
  • 2. 通过 `==`与 `is`理解可变对象与不可变对象
  • 二、深复制和浅复制
  • (一)引用
  • 1. `变量1 = 变量2`
  • 2. 可变对象与不可变对象
  • (二)浅复制
  • (三)深复制
  • 三、参考资料
  • 一、准备

    (一)对象

    1. 对象的特性

    Python对象都拥有三个特性:身份、类型、值

  • 身份:对象的唯一标识,可通过函数 id()来查看,一般情况下可认为是对象所在的内存地址
  • 类型:对象的类型决定有什么属性和方法,可以通过函数 type()查看对象的类型
  • 值:对象保存的值,可以通过变量来获取
  • # 对象的身份
    >>> id('abc')
    2537891308144
    
    # 对象的类型
    >>> type('abc')
    <class 'str'>
    
    # 对象的值
    >>> x = 'abc'
    >>> x
    'abc'
    
    2. 常见的Python内置类型
  • 整型 int
  • 浮点型 float
  • 字符串 str
  • 列表 list
  • 元组 tuple
  • 字典 dict
  • 集合 set
  • 3. 可变对象与不可变对象

    在Python中,按更新对象的方式,可以将对象分为2大类:可变对象不可变对象

    常见类型 身份
    可变对象 列表、字典、集合 不可变 可变
    不可变对象 数字、字符串、元组 不可变 不可变

    (二)变量与对象

    Python中的变量可以理解成指向对象的一个指针,变量和对象之间的关系为引用。

    变量本身是没有类型的,类型只存在于对象中,变量只是引用了对象。

    例如赋值语句 x = 'abc',可以理解为:Python先在内存中创建一个对象 'abc',然后将变量 x指向 'abc'

    变量赋值

    1. 可变对象与不可变对象的创建
  • 在创建不可变对象时,如果命名空间中已经存在值相同的对象,则不重复创建,将变量指向已经存在的对象;
  • 对于可变对象,即使要创建的对象和已有对象的值相同,也会重新创建一个新的对象。
  • PS:

  • 因为不可变对象的值不可变,所以没有必要再重复创建一个相同的对象;
  • 因为可变对象的值是允许改变的,所以需要创建一个新的对象。
  • 2. 通过 ==is理解可变对象与不可变对象
  • ==(比较运算符):主要用于判断两个对象的是否相等
  • is(同一性运算符):主要用于判断两个对象的身份是否相等
  • >>> x1 = 'abc'
    >>> x2 = 'abc'
    >>> x1 == x2
    True
    >>> id(x1), id(x2)
    (2537891308144, 2537891308144)
    >>> x1 is x2
    True
    
    >>> y1 = [1, 2, 3]
    >>> y2 = [1, 2, 3]
    >>> y1 == y2
    True
    >>> id(y1), id(y2)
    (2537894283200, 2537894283584)
    >>> y1 is y2
    False
    

    在这里可以看到 x1x2引用的地址相同,即引用的对象身份相同,不可变对象 'abc'只有一个,没有重复创建;

    y1y2引用的地址不同,即引用的是两个不同的对象,尽管他们的值是相等的。

    可变与不可变

    二、深复制和浅复制

    (一)引用

    1. 变量1 = 变量2
    >>> x1 = 'abc'
    >>> x2 = x1
    >>> id(x1), id(x2)
    (2441453576816, 2441453576816)
    
    >>> y1 = [1, 2, 3]
    >>> y2 = y1
    >>> id(y1), id(y2)
    (2441460811904, 2441460811904)
    

    通过这个例子可以看到,不论是可变对象还是不可变对象,使用 =并没有进行复制,只是将两个变量指向了同一个对象。

    2. 可变对象与不可变对象
    1. 不可变对象

    因为对象不可变,所以无法改变相应的值,但是有以下易引起混淆的情况:

    >>> x1 = 'abc'
    >>> x2 = x1
    >>> x1 == x2
    True
    >>> id(x1), id(x2)
    (2441453576816, 2441453576816)
    >>> x2 = 'qwerty'
    >>> id(x1), id(x2), id('qwerty')
    (2441453576816, 2441460831792, 2441460831792)
    

    这里的 x2 = 'qwerty'不是改变对象值的操作,其实是一个赋值语句,相当于重新把 x2指向 'qwerty'这个对象,可以看到 x2'qwerty'的地址是相同的。

    1. 可变对象
    >>> x1 = [1, 2, 3]
    >>> x2 = x1
    >>> id(x1), id(x2)
    (2441460819968, 2441460819968)
    
    >>> x2.append(4)
    >>> x1
    [1, 2, 3, 4]
    >>> x2
    [1, 2, 3, 4]
    >>> id(x1), id(x2)
    (2441460819968, 2441460819968)
    

    这里在将 x1x2指向同一个列表后,通过 x2改变列表的值,可以发现在 x1也一起发生了变化,而且二者指向的还是原来的对象,只是原来的对象的值发生了变化。

    下面再看一个例子,进一步理解列表的结构:

    >>> y1, y2, y3 = 1, 2, 3
    >>> x1 = [1, 2, 3]
    >>> x2 = [2, 3, 1]
    
    >>> id(x1), id(x2)
    (2396653057920, 2396655945856)
    
    >>> id(1), id(2), id(3)
    (2396647614768, 2396647614800, 2396647614832)
    
    >>> id(y1), id(y2), id(y3)
    (2396647614768, 2396647614800, 2396647614832)
    
    >>> id(x1[0]), id(x1[1]), id(x1[2])
    (2396647614768, 2396647614800, 2396647614832)
    
    >>> id(x2[2]), id(x2[0]), id(x2[1])
    (2396647614768, 2396647614800, 2396647614832)
    

    通过这个例子可以发现,两个列表由相同的元素组成(元素都是不可变的类型),同时地址不同,表示不是同一个对象,

    但是内部元素都是id相同的对象(指向相同的地址),其中的结构可以按下图演示的来理解:

    列表的结构

    (二)浅复制

    浅复制只复制最外面一层对象本身,对内部的元素仍只是进行引用。

    浅复制有三种形式:

  • 切片操作
  • 工厂函数
  • copy模块中的copy函数
    1. 不可变对象

    如上所述,因为对象不可变,所以不会另外重新创建一个新的对象用于存储相同的内容,所以复制后变量还是指向原来的对象。

    >>> import copy
    >>> x0 = 'abc'
    
    >>> x1 = x0[:]
    >>> x2 = str(x0)
    >>> x3 = copy.copy(x0)
    
    >>> x0, x1, x2, x3
    ('abc', 'abc', 'abc', 'abc')
    
    >>> id(x0), id(x1), id(x2), id(x3)
    (2996286333552, 2996286333552, 2996286333552, 2996286333552)
    

    可以看到三种浅复制方式后的变量仍指向相同的id。

    再看一个元组的例子:

    >>> x0 = (1, 2, 3)
    >>> x1 = x0[:]
    >>> x2 = tuple(x0)
    >>> x3 = copy.copy(x0)
    >>> x0, x1, x2, x3
    ((1, 2, 3), (1, 2, 3), (1, 2, 3), (1, 2, 3))
    >>> id(x0), id(x1), id(x2), id(x3)
    (2996291018048, 2996291018048, 2996291018048, 2996291018048)
    
    1. 可变对象

    对于可变对象的浅复制,仍以列表为例,先看一个简单的列表:

    >>> x0 = [1, 2, 3]
    >>> x1 = x0[:]
    >>> x2 = list(x0)
    >>> x3 = copy.copy(x0)
    >>> x0, x1, x2, x3
    ([1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3])
    >>> id(x0), id(x1), id(x2), id(x3)
    (2996293512000, 2996293523328, 2996293605632, 2996293503424)
    
    >>> x2.append(5)
    >>> x0, x1, x2, x3
    ([1, 2, 3], [1, 2, 3], [1, 2, 3, 5], [1, 2, 3])
    

    可以看到浅复制后,每个变量都指向的不同的对象,而且其中一个列表 x2改变对象的值后没有影响到其他的对象。

    再看一个嵌套的列表:

    >>> x0 = [1, 2, ['a', 'b', 'c'], 4, 5]
    >>> x1 = x0[:]
    >>> x2 = list(x0)
    >>> x3 = copy.copy(x0)
    
    >>> x3[0] = 999
    >>> x3[2][1] = 666
    
    >>> x0
    [1, 2, ['a', 666, 'c'], 4, 5]
    >>> x1
    [1, 2, ['a', 666, 'c'], 4, 5]
    >>> x2
    [1, 2, ['a', 666, 'c'], 4, 5]
    >>> x3
    [999, 2, ['a', 666, 'c'], 4, 5]
    
    >>> id(x0), id(x1), id(x2), id(x3)
    (2996293512000, 2996293522944, 2996293605952, 2996293606464)
    >>> id(x0[2]), id(x1[2]), id(x2[2]), id(x3[2])
    (2996293605888, 2996293605888, 2996293605888, 2996293605888)
    

    可以看到,再复制后对列表最外一层的数值进行修改没有对其他的对象产生影响,但是对内层嵌套的列表值进行的修改影响到了其他的对象,同时查看嵌套的列表的id是相同的,
    所以浅复制仅对最外一层的对象进行复制,内层嵌套的对象不进行复制,仍只进行引用。

    再看一个嵌套了列表的元组:

    >>> x0 = (1, 2, ['a', 'b', 'c'], 4, 5)
    >>> x1 = x0[:]
    >>> x2 = tuple(x0)
    >>> x3 = copy.copy(x0)
    
    >>> id(x0), id(x1), id(x2), id(x3)
    (2996293559376, 2996293559376, 2996293559376, 2996293559376)
    >>> id(x0[2]), id(x1[2]), id(x2[2]), id(x3[2])
    (2996293606464, 2996293606464, 2996293606464, 2996293606464)
    
    >>> x3[2][1] = 666
    >>> x0
    (1, 2, ['a', 666, 'c'], 4, 5)
    >>> x1
    (1, 2, ['a', 666, 'c'], 4, 5)
    >>> x2
    (1, 2, ['a', 666, 'c'], 4, 5)
    >>> x3
    (1, 2, ['a', 666, 'c'], 4, 5)
    

    因为元组是不可变对象,最外层的元素不可变,所以复制后变量还是指向相同的对象;
    因为是浅复制,所以没有对内层的嵌套进行复制,对其中一个进行改变,其他的也随之改变。

    (三)深复制

    深复制对对象的所有元素进行复制,包括其中多层嵌套的元素,所以深复制前后的对象没有任何关系。

    还是先看一个列表的例子:

    >>> x0 = [1, 2, ['a', 'b', 'c'], 4, 5]
    >>> x1 = copy.deepcopy(x0)
    >>> x0
    [1, 2, ['a', 'b', 'c'], 4, 5]
    >>> x1
    [1, 2, ['a', 'b', 'c'], 4, 5]
    >>> id(x0), id(x1)
    (2996294836480, 2996294521856)
    >>> x0[2], x1[2]
    (['a', 'b', 'c'], ['a', 'b', 'c'])
    >>> id(x0[2]), id(x1[2])
    (2996297157312, 2996297490688)
    >>> id(x0[1]), id(x1[1]) # 不可变对象的id不变
    (2996285368656, 2996285368656)
    
    >>> x1[0] = 666
    >>> x0[2][2] = 999
    >>> x0
    [1, 2, ['a', 'b', 999], 4, 5]
    >>> x1
    [666, 2, ['a', 'b', 'c'], 4, 5]
    

    可以看到在深复制后的对象与复制前已经完全不同,包括内层的元素,对内部元素的改变也没有互相影响,
    但是对于不可变类型的对象,仍像前面提到的那样id是不变的。

    再看两个元组的例子:

    # 例1
    >>> x0 = (1, 2, 3)
    >>> x1 = copy.deepcopy(x0)
    >>> id(x0), id(x1)
    (2996297494272, 2996297494272)
    
    # 例2
    >>> x0 = (1, 2, ['a', 'b', 'c'], 4, 5)
    >>> x1 = copy.deepcopy(x0)
    
    >>> id(x0), id(x1)
    (2996293581056, 2996297505024)
    >>> id(x0[2]), id(x1[2])
    (2996294836480, 2996294521856)
    
    >>> x0[2][2] = 999
    >>> x1[2][1] = 666
    >>> x0
    (1, 2, ['a', 'b', 999], 4, 5)
    >>> x1
    (1, 2, ['a', 666, 'c'], 4, 5)
    

    可以看到 (1, 2, 3)内外都是不可变的对象,所以即便深复制后两个变量还是指向同一个对象,
    但是对于 (1, 2, ['a', 'b', 'c'], 4, 5),因为元组内部包含可变类型的对象 ['a', 'b', 'c'],所以在深复制后两个变量指向了不同的对象,包括内部的列表也是。

    三、参考资料

    https://baijiahao.baidu.com/s?id=1627356407968660842
    https://www.cnblogs.com/xueli/p/4952063.html
    https://www.jianshu.com/p/9ed9b5ce7bb0
    https://my.oschina.net/u/2291665/blog/858552
    https://zhuanlan.zhihu.com/p/265179766
    https://blog.csdn.net/bufengzj/article/details/90486991
    https://zhuanlan.zhihu.com/p/142826749
    https://zhuanlan.zhihu.com/p/54011712
    https://blog.csdn.net/SAKURASANN/article/details/102882383
    https://www.zhihu.com/question/20670869

    作者:student-jz

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python中的深复制与浅复制机制详解

    发表回复