373-Python中的深浅拷贝
Python中的深浅拷贝
- 变量:是一个系统表的元素,拥有指向对象的连接空间
- 对象:被分配的一块内存,存储其所代表的值
- 引用:是自动形成的从变量到对象的指针
- 类型:属于对象,而非变量
- 不可变对象:一旦创建就不可修改的对象,包括数值类型、字符串、布尔类型、元组
(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)
- 可变对象:可以修改的对象,包括列表、字典、集合
(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)
0.赋值
赋值: 只是复制了新对象的引用,不会开辟新的内存空间。
并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。
1.浅拷贝
定义(只有最外层容器是新的,内部元素都是共享的)
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存(分支)。
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
浅拷贝: 创建新对象,其内容是原对象的引用。
浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。
Python浅拷贝实验
可变数据类型的浅拷贝
栗子1
import copy
a = [1,2,3]
b = a # 传统赋值
c = a[:] # 切片(底层就是浅拷贝)
d = copy.copy(a) # 浅拷贝
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"d的id: {id(d)}")
print(f"a[0]的id: {id(a[0])}")
print(f"b[0]的id: {id(b[0])}")
print(f"c[0]的id: {id(c[0])}")
print(f"d[0]的id: {id(d[0])}")
控制台输出:
a的id: 2596354113728
b的id: 2596354113728
c的id: 2596354069056
d的id: 2596355427712
a[0]的id: 140716802828728
b[0]的id: 140716802828728
c[0]的id: 140716802828728
d[0]的id: 140716802828728
栗子2
import copy
# 创建对象
a = [1, 2, 3]
b = [4, 5, 6]
data = [a, b]
# 传统赋值
c = data
# 浅拷贝
d = copy.copy(data)
# 验证
print(f"data的id: {id(data)}")
print(f"c的id: {id(c)}") # 与data相同
print(f"d的id: {id(d)}") # 与data不同
print(f"\ndata[0]的id: {id(data[0])}")
print(f"d[0]的id: {id(d[0])}") # 与data[0]相同
控制台输出:
data的id: 2024771407424
c的id: 2024771407424
d的id: 2024771282944
data[0]的id: 2024770027712
d[0]的id: 2024770027712
不可变数据类型的浅拷贝
栗子1
import copy
a = (1,2,3)
b = a # 传统赋值
c = a[:] # 切片(底层就是浅拷贝)
d = copy.copy(a) # 浅拷贝
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"d的id: {id(d)}")
print(f"a[0]的id: {id(a[0])}")
print(f"b[0]的id: {id(b[0])}")
print(f"c[0]的id: {id(c[0])}")
print(f"d[0]的id: {id(d[0])}")
控制台输出:
a的id: 1958923423680
b的id: 1958923423680
c的id: 1958923423680
d的id: 1958923423680
a[0]的id: 140716802828728
b[0]的id: 140716802828728
c[0]的id: 140716802828728
d[0]的id: 140716802828728
栗子2
import copy
# 创建对象
a = (1, 2, 3)
b = (4, 5, 6)
data = (a, b)
# 传统赋值
c = data
# 浅拷贝
data_copy = copy.copy(data)
# 验证所有引用
print("验证外层元组:")
print(f"data的id: {id(data)}")
print(f"c的id: {id(c)}") # 与data相同
print(f"data_copy的id: {id(data_copy)}") # 与data相同
print("\n验证内部元素:")
print(f"data[0]的id: {id(data[0])}")
print(f"c[0]的id: {id(c[0])}") # 与data[0]相同
print(f"data_copy[0]的id: {id(data_copy[0])}") # 与data[0]相同
对于不可变类型(如元组),copy.copy()不会创建新的对象,这是因为不可变对象是安全的,不需要创建副本。
对于元组这样的不可变类型来说,浅拷贝和简单赋值的效果是完全一样的
控制条输出:
验证外层元组:
data的id: 1714974555456
c的id: 1714974555456
data_copy的id: 1714974555456
验证内部元素:
data[0]的id: 1714974379968
c[0]的id: 1714974379968
data_copy[0]的id: 1714974379968
浅拷贝有三种形式:
- 切片操作(底层用copy.copy实现)
- 工厂函数(list())
- copy模块中的copy函数。
浅拷贝的笔试题
a = [1, 3, 5, [7, 9]]
b = a[:]
a[1] = 2
a[3][1] = 10
print(b) # 问b会打印出什么来
答案:[1, 3, 5, [7, 10]]
2.深拷贝
定义
深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)
- 拷贝第一层级的对象属性或数组元素
- 递归拷贝所有层级的对象属性和数组元素
- 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
Python深拷贝实验
可变数据类型的深拷贝
import copy
a = [1, 2, 3, [4, 5, 6]]
b = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
控制台输出:
a的id: 1467343770176
b的id: 1467345194368
a[3]的id: 1467343814848
b[3]的id: 1467345070464
结论:
- 对于简单的可变数据类型,深拷贝可以对对象进行完全拷贝,生成一块独立的内存空间,两个变量之间没有任何关系
- 对于复杂可变数据类型,深拷贝会递归拷贝所有层级的对象属性和数组元素,生成一块独立的内存空间,且两个变量之间没有任何关系
不可变数据类型的深拷贝
栗子1
import copy
a = (1, 2, 3)
b = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
控制台输出:
a的id: 2423245393856
b的id: 2423245393856
栗子2
import copy
a = (1, 2, 3, (4, 5, 6))
b = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
控制台输出:
a的id: 2632243691280
b的id: 2632243691280
a[3]的id: 2632243695552
b[3]的id: 2632243695552
结论:
对于不可变数据类型,深拷贝也只能拷贝对象的引用关系,结果就是指向相同内存空间
3.深浅拷贝的特殊情况
可变类型嵌套不可变类型
实验:
import copy
a = [1, 2, 3, (4, 5, 6)]
b = copy.copy(a)
c = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
print(f"c[3]的id: {id(c[3])}")
控制台输出:
a的id: 1753060524224
b的id: 1753060479552
c的id: 1753061903744
a[3]的id: 1753061609408
b[3]的id: 1753061609408
c[3]的id: 1753061609408
结论:外层可变类型可以生成内存空间完全拷贝,但是内层对象是不可变类型,只能拷贝引用关系
不可变类型嵌套可变类型⭐
实验:
import copy
a = (1, 2, 3, [4, 5, 6])
b = copy.copy(a)
c = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
print(f"c[3]的id: {id(c[3])}")
控制台输出:
a的id: 2603735950784
b的id: 2603735950784
c的id: 2603735950864
a[3]的id: 2603734778048
b[3]的id: 2603734778048
c[3]的id: 2603736157568
特殊结论:对于深拷贝,由于内层是可变类型需要拷贝,导致外层不可变类型也被拷贝,导致整体都可以进行完全拷贝
深浅拷贝总结
赋值: 值相等,地址相等
copy浅拷贝:值相等,地址不相等
deepcopy深拷贝:值相等,地址不相等