赋值、浅拷贝、深拷贝
总结
- 赋值: 新建一个原来对象内存地址的引用,对象本身不开辟新的内存空间;
- 浅拷⻉: 新建多个原来对象内一级子元素内存地址的引用,对象本身需要开辟新的内存空间;
- 深拷⻉: 复制原来对象内的所有N级子元素的数据,所有的数据都开辟新的内存空间。
一、概念解读
赋值:旧瓶装旧酒
对于复制的操作,最简单的就是赋值,指的是新建一个对象的引用,新建目标对象与原来的目标对象指向同一个内存地址,(整体指向)赋值只相当于增加了一个引用,并没有开辟新的内存空间。
因此,一个变另一个也会变。
浅拷贝:新瓶装旧酒
浅拷⻉顾名思义就是拷⻉的比较浅,我们可以把赋值认为是新建了一个对象的引用,把原来被对象内存空间的数据指向新的变量,这时同一块内存空间指向两个变量。
浅拷⻉与赋值不同,既然是拷⻉,那么就是要开辟一块新的内存空间,复制的是原来被拷⻉对象内多个元素对象的引用,有几个元素对象就赋值几个元素对象的引用。
不过,如果旧的酒瓶里面如果还套了一个酒瓶子,那你就需要注意了。因此,不论是对拷⻉对象或者是被拷⻉对象内一个可变类型元素内的元素的修改(包含添加,修改和删除),都会引起另外一方的变化。
但是,如果是直接对对象内的一级元素当作一个整体进行修改(这时对可变类型元素的元素只能是删除),虽然他们的元素有共享的内存地址,但是拷⻉对象或者被拷⻉对象元素内的引用都是独立的,删除了其中一方的一个引用,另外一方的引用依然存在。
因此,若嵌套了瓶子,则瓶子内部变,两者都变;单独元素变,则另一个不变。
深拷贝:新瓶装新酒
深拷⻉其实与浅拷⻉有本质的区别,
-
它不会复制任何的引用。
-
对象内的所有元素,子元素,孙子元素,重孙元素,曾孙元素的数据都是由复制而来。
因此,这样的操作被称为“新瓶装新酒”。它的实现原理就是递归,只要任意元素内仍然有子元素,就会复制子元素的数据放到新的内存地址。既然这样,在使用深拷⻉后,被拷⻉对象的改变,不会引起拷⻉对象的任何改变。
二、实例测试
测试方法
- 测试1:变量名的内存地址是否相同、变量里的一级元素的内存地址是否相同
- 测试2:当变量里的一级元素发生变化时,另一个变量内部的值是否相同
- 测试3:当变量里的二级元素发生变化时,另一个变量是否也会变量。
二级元素: x = [1,[2,3]] 相对于x来说,1是一级元素,2是二级元素。几级元素对应的数据结构深度。
def test(list1,list2):
# 测试1
print(list1 is list2)
print(list1[0] is list2[0])
# 测试2
list1[0] = 10
print('list1:',list1,'\nlist2:',list2)
# 测试3
list1[2][0] = '1->0'
print('list1:',list1,'\nlist2:',list2)
赋值
list1 = [1, 2, [3, 4], [5, [6, 7]]]
print('list1:', id(list1))
print([id(i) for i in list1])
list2 = list1
print('list2:', id(list2))
print([id(i) for i in list2])
# 测试
test(list1,list2)
测试结果
list1: 140553076882496
[9788608, <