python-深拷贝和浅拷贝

变量: 存储对象的引用

对象:会分配一块内存空间,存储实际数据

引用:变量指向对象,理解为指针

变量存储在栈内存,对象存储在堆内存。

Python数据类型分为可变数据类型和不可变数据类型。

  • 可变数据类型包括:List(列表)、Dictionary(字典)、Set(集合)
  • 不可变数据类型包括:String(字符串)、Number(数字)、Tuple(元组)
  • Python 的赋值语句并不是创建一个新对象,只是创建了一个共享原始对象引用的新变量

    不可变对象的赋值

    a = 1
    b = a
    print(a, b)
    a += 2
    print(a, b)
    print("a id:", id(a))
    print("b id:", id(b))
    # 输出结果
    a id: 4564097808
    b id: 4564097776
    
  • 修改变量 a 的值,不会同步修改变量 b 的值
  • 因为赋值操作 a += 2 后,变量 a 存储的对象引用已经改变了
  • 可变对象的赋值

    当其中一个内数据改变后,另一个也改变了

    a = [1, 2, 3]
    b = a
    print(a, b)
    a[1] = 22
    print(a, b)
    print("a id:", id(a))
    print("b id:", id(b))
    # 输出结果
    [1, 2, 3] [1, 2, 3]
    [1, 22, 3] [1, 22, 3]
    a id: 4567683136
    b id: 4567683136
    
  • 修改 a 变量的值,会同步修改变量 b 的值,因为变量 a、b 指向的对象是可变对象,所以它们保存的对象引用都是一样的
  • 浅拷贝会创建一个新对象,该新对象存储原始元素的引用

  • (1)不拷贝子对象的内容,只拷贝子对象的引用
  • (2)可以使用Python内置函数copy()
  • 浅拷贝的三种形式

    切片-可变序列

    lst = [1,2,[3,4]]

    list1=[1,2,3]
    list2=list1[:]
    

    切片是属于浅拷贝,修改数据时,会对原先的造成污染

    对于不可变数据类型(元组、字符串):

    元组 使用 tuple()和切片操作符:

    字符串 使用 str()和切片操作符:

    都不是浅拷贝,没有开辟新内存来存储原对象对子对象的引用

    列表的切片是浅拷贝(创建新的内存空间),元组的切片是赋值(不会创建新的内存空间)

    切片操作符不能用于字典和集合完成浅拷贝

    工厂函数-构造器

    使用数据类型本身构造器

    lst1 = list(lst)

    #列表list
    list1=[1,2,3]
    list2=list(list1)
    set1=([1,2,3])
    set2=set(set1)
    #字典dict
    dict1={1:[1,'w'],2:33}
    dict2=dict(dict1)
    

    copy.copy()

    list1=[1,2,3]
    list2=list1.copy()
    import copy  #需要先导入copy模块
    list1=[1,2,3]
    list2=copy.copy(list1)
    

    浅拷贝的原对象的数据改变

    浅拷贝在拷贝时,只会copy一层,在内存中开辟一个空间,存放这个copy的列表。更深的层次并没有copy,即第二层用的都是同一个内存。

    list1=[(1,2),3,[4,5]]
    list2=list1.copy()
    list1.append(31)
    print(list1)
    print(list2)
    [(1, 2), 3, [4, 5], 31]
    [(1, 2), 3, [4, 5]]
    list1=[(1,2),3,[4,5]]
    list2=list1.copy()
    list2.append(31)
    print(list1)
    print(list2)
    [(1, 2), 3, [4, 5]]
    [(1, 2), 3, [4, 5], 31]
    list1=[(1,2),3,[4,5]]
    list2=list1.copy()
    list1[0]+=(31,31)
    print(list1)
    print(list2)
    [(1, 2,31,31), 3, [4, 5]]
    [(1, 2), 3, [4, 5]]
    list1=[(1,2),3,[4,5]]
    list2=list1.copy()
    list1[2]+=[31,31]
    print(list1)
    print(list2)
    [(1, 2), 3, [4, 5,31,31]]
    [(1, 2), 3, [4, 5,31,31]]
    
  • (1)会连子对象的内存全部拷贝一份,对子对象的修改不会影响源对象
  • (2)可以使用Python内置函数deepcopy()
  • import copy
    list_01 = ["11","22"]
    list_02 = ["33",list_01]
    list_03 = copy.deepcopy(list_02)
    print(id(list_01))
    print(id(list_02))
    print(id(list_03))
    print(id(list_02[1]))
    print(id(list_03[1]))
    list_01.append("44")
    print(list_01)
    print(list_02)
    print(list_03)
    ['33', ['11', '22', '44']]
    ['33', ['11', '22']]
    

    通过深拷贝即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。

  • 浅拷贝花费时间少,占用内存少,只拷贝顶层数据,拷贝效率高。
  • 对不可变对象拷贝时,浅拷贝和深拷贝的作用是一致的,不开辟新空间,相当于赋值操作。
  • 可变对象浅拷贝时,只拷贝第一层中的引用,如果元素是可变对象,并且被修改,那么拷贝的对象也会发生变化。
  • 可变对象深拷贝时,会逐层进行拷贝,遇到可变类型,就开辟一块内存复制下来,不论哪一层元素被修改,拷贝的对象都不会发生改变。
  • 不可变对象可以直接赋值,可变对象只有一层时,建议浅拷贝,如果可变对象是多层,建议深拷贝。