python:深拷贝与浅拷贝
一、了解几个概念
变量:是一个系统表的元素,拥有指向对象的连接空间
对象:被分配的一块内存,存储所代表的值
引用:是自动形成的从变量到对象的指针
类型:属于对象,而非变量
不可变对象:一旦创建就不可修改的对象(值内存地址固定后不可以再修改其值),包括字符串、元组、数值类型(整型、浮点型)、布尔类型
该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象:可以修改的对象(值内存地址固定后还可以修改其值),包括列表、字典、集合
该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。
当我们写:
a = 'python'
① 创建变量 a (栈)
② 创建一个对象(分配一块内存空间),来存储值“python” (数据区)
③ 将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)
image-20231019215615031
赋值:只是复制了新对象的引用,不会开辟新的内存空间(将原有数据打上新的标签)
image-20231019220233726
二、浅拷贝
拷贝就是复制操作,其拷贝的值是一样的,重点观察引用关系的变化
浅拷贝:创建新对象,其内容是原对象的引用
浅拷贝之所以称为浅拷贝,是因为它仅仅只拷贝一层,拷贝了最外围的对象本身,内部的元素都只是拷贝一个引用而已。
基本语法:
import copy
copy.copy(要拷贝的变量名称)
可变数据类型的浅拷贝:
import copy
对于可变数据类型的浅拷贝
list1 = [1, 3, 5]
list2 = copy.copy(list1)
print(list1, id(list1)) # [1, 3, 5] 2029785917952
print(list2, id(list2)) # [1, 3, 5] 2029788974656
对于简单的可变数据类型,浅拷贝相当于将原对象的值进行拷贝,需要在内存空间中开辟一块新的内存空间
image-20231019221633149
import copy
list1 = [1, 3, 5, [7, 9]]
list2 = copy.copy(list1)
print(list1, id(list1)) # [1, 3, 5, [7, 9]] 1757227908928
print(list2, id(list2)) # [1, 3, 5, [7, 9]] 1757227822720
但是对于内层[7.9],这也是一个列表,也需要占用内存地址,那么其拷贝过程的内存是否相同呢
print(id(list1[3])) # 2710364155712
print(id(list2[3])) # 2710364155712
对于复杂的可变数据类型,浅拷贝只能拷贝可变数据类型的最外层对象,而无法拷贝内层对象,所以只需要为最外层对象开辟内存空间,内层对象拷贝之后的引用关系和原对象保持不变
image-20231019222741554
结论:浅拷贝能力有限,只能拷贝最外层对象(只需要为最外层对象开辟内存空间),而无法拷贝内层对象
不可变数据类型的浅拷贝
import copy
tuple1 = (1, 3, 5)
tuple2 = copy.copy(tuple1)
print(tuple1, id(tuple1)) # (1, 3, 5) 1831160185728
print(tuple2, id(tuple2)) # (1, 3, 5) 1831160185728
对于简单的不可数据类型,由于不可变数类型地址一旦固定,其值就无法改变了,又由于浅拷贝需要把自身的对象空间赋值给另外一个对象,为了保持数据一致,只能让其指向相同的内存空间(不需要额外开辟内存空间)
image-20231019224248298
对复杂的不可变数据类型,浅拷贝也只能拷贝最外层对象,无法拷贝内层对象
浅拷贝的三种形式:
切片操作、工厂函数(数据类型转换)、copy模块中的copy函数
切片操作:list1 = list[ : ] 或 list1 = [i for i in list ]
工厂函数:list1 = list(list2)
copy函数:list1 = copy.copy(list)
浅拷贝案例:
a = [1, 3, 5, [7, 9]]
b = a[:]
a[1] = 2
a[3][1] = 10
print(b)
问b输出结果多少?
[1, 3, 5, [7, 10]]
浅拷贝小结:
1)当浅复制的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。
2)当浅复制的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:
第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。
第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。
三、深拷贝
深拷贝:与浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关系。
所以改变原有被复制对象不会对已经复制出来的新对象产生影响。
深拷贝只有一种形式:使用copy模块中的deepcopy函数
import copy
data = copy.deepcopy(data)
简单的可变类型深拷贝:
import copy
a = [1, 3, 5]
b = copy.deepcopy(a)
print(a, id(a)) # [1, 3, 5] 2434688069312
print(b, id(b)) # [1, 3, 5] 2434690765888
复杂的可变类型深拷贝:
import copy
a = [1, 3, 5, [7, 9]]
b = copy.deepcopy(a)
print(a, id(a)) # [1, 3, 5, [7, 9]] 2162810741504
print(b, id(b)) # [1, 3, 5, [7, 9]] 2162810727424
print(id(a[3])) # 2162810727040
print(id(b[3])) # 2162810727232
对于可变数据类型的深拷贝,深拷贝拷贝了所有的数据并开辟对应的内存空间储存。
不可变数据类型的深拷贝:
import copy
a = (1, 3, 5, (7, 9))
b = copy.deepcopy(a)
print(a, id(a)) # (1, 3, 5, (7, 9)) 2029346995712
print(b, id(b)) # (1, 3, 5, (7, 9)) 2029346995712
print(id(a[3])) # 2029346562432
print(id(b[3])) # 2029346562432
对于不可变数据类型的深拷贝,不管是简单的还是复杂的,深拷贝都只能对象的引用关系,他们指向了相同的内存空间
四、深浅拷贝中的特殊情况
案例1:可变嵌套不可变数据类型
import copy
a = [1, 3, 5, (7, 9)]
b = copy.copy(a)
c = copy.deepcopy(a)
print(id(a)) # 1186799997824
print(id(b)) # 1186799988352
print(id(c)) # 1186799988672
print(id(a[3])) # 1700778811776
print(id(b[3])) # 1700778811776
print(id(c[3])) # 1700778811776
外层对象是可变数据类型,所以可以进行完全拷贝(需要生成内存空间),但是内层对象是不可变数据类型,所以只能进行拷贝引用关系
案例2:不可变嵌套可变数据类型
d = (1, 3, 5, [7, 9])
e = copy.copy(d)
f = copy.deepcopy(d)
print(id(d)) # 2193468143712
print(id(e)) # 2193468143712
print(id(f)) # 2193468262576 内存地址不一样了
print(id(d[3])) # 2764873530496
print(id(e[3])) # 2764873530496
print(id(f[3])) # 2764873530560
如果一个不可变数据类型包含了可变数据类型,浅拷贝的结论与之前一致,都只能拷贝引用关系。但是对于深拷贝而言,这种数据类型整体都可以进行完全拷贝
image-20231020165800762