1. Python如何拷贝一个对象?
在Python中如何拷贝一个对象呢?我们很多时候会用等号赋值法,除了=赋值,实际上还有浅拷贝和深拷贝,那么赋值,浅拷贝和深拷贝到底有什么区别呢?
(1) 赋值(=):创建了对象的一个新的引用,修改其中任意一个变量都会影响到另外一个;
(2) 浅拷贝:创建一个新的对象,但是它包含的是对原始对象中包含项的引用(如果用引用的方式修改其中一个对象,另外一个也会改变),即浅拷贝只拷贝顶层引用;
(3) 深拷贝:创建一个新对象,并且递归的复制它所包含的对象(修改其中一个,另外一个不会改变),即深拷贝会逐层进行拷贝,直到拷贝的所有引用都是不可变引用为止。
浅拷贝和深拷贝从概念上看有点抽象,我们可以通过下面的例子来说明:
2. 举例说明
例如有列表 l1=['aaa', 66, [1, 2, 3]],l2是对l1的赋值,l3是对l1的浅拷贝,l4是对l1的深拷贝。
>>> l1 = ['aaa', 66, [1, 2, 3]]
>>> l2 = l1
>>> l3 = l1.copy()
>>> l4 = copy.deepcopy(l1)
>>> id(l1)
140214926880712 ------> 指向同一块内存地址
>>> id(l2)
140214926880712 ------> 赋值是拷贝引用,指向同一块内存地址
>>> id(l3)
140215054238536 ------> 创建新的对象,即分配新的内存地址
>>> id(l4)
140214924504200
再打印列表l1的第3个元素的id:
>>> id(l1[2])
140214926939592
>>> id(l2[2])
140214926939592 ------> 赋值后第三个元素指向[1, 2, 3]的地址 可变对象
>>> id(l3[2])
140214926939592 ------> 浅拷贝后第三个元素指向[1, 2, 3]的地址 可变对象,这就是所谓的浅拷贝,只拷贝顶层引用
>>> id(l4[2])
140214926938440 ------> 深拷贝赋值所有的内容,所以第三个元素的内存地址与赋值和浅拷贝均不同,这就是所谓的深拷贝,递归赋值
修改l1的第3个元素,查看赋值,浅拷贝,深拷贝的列表值的变化
>>> l1[2][0] = 4
>>> l1
['aaa', 66, [4, 2, 3]]
>>> l2
['aaa', 66, [4, 2, 3]] ------> 因为赋值l2是拷贝的l1的引用,l1的元素变化l2会跟着变化
>>> l3
['aaa', 66, [4, 2, 3]] ------> 因为浅拷贝l3的第三个元素拷贝的引用,故l3的第三个元素也会变化
>>> l4
['aaa', 66, [1, 2, 3]] ------> 可以看出深拷贝并不会因为l1的变化而影响,因为深拷贝会逐层进行拷贝,直到拷贝的所有引用都是不可变引用为止
接下来在列表l1追加一个元素,观察赋值,浅拷贝,深拷贝的列表值的变化:
>>> l1.append(88)
>>> l1
['aaa', 66, [4, 2, 3], 88]
>>> l2
['aaa', 66, [4, 2, 3], 88] -------> 同理因为赋值l2是拷贝的l1的引用,l1的元素变化l2会跟着变化
>>> l3
['aaa', 66, [4, 2, 3]] -------> 到这里应该就容易理解了吧,因为浅拷贝l3的内存是新分配的,故l1追加元素并不会影响浅拷贝l3的值
>>> l4
['aaa', 66, [1, 2, 3]] -------> 和我们预期的一样,深拷贝依然不受影响
在这里补充下例子中的浅拷贝,在Python中一切皆是对象,通过概念我们可以看出浅拷贝是创建了一个新的对象,但是这个新对象中的内容仍然是指向原始对象中包含项的引用,可以理解为浅拷贝是新对象,但是对象的包含项仍然是旧的引用。所以上面例子中,l1[2][0] =4 修改列表l1后浅拷贝的第3个元素会随着l1的改变而变化,因为[1,2,3]本身就是个可变的列表,其在初始化时内存地址已经分配好,这个把内存地址拷贝给了l3;而 l1.aapend(88)运行后l3列表并没有追加该元素,因为l3是个对象,是个新对象,与l1的内存地址不同,所以不会受影响。不知道我这里有没有讲清楚。
3. 总结:
赋值:默认浅拷贝传递对象的引用而已,原始列表改变,被赋值的b也会做相同的改变;
浅拷贝:没有拷贝子对象,所以原始数据改变,子对象会改变
深拷贝:包含对象里面的子对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变
在其他帖子看到对浅拷贝和深拷贝这样的总结,挺有意思的:
浅拷贝就是藕断丝连 ----只拷贝顶层引用,子项还存在引用关系
深拷贝就是离婚了 ----彻底没关系了
为什么Python默认的拷贝方式是浅拷贝?
时间角度:浅拷贝花费时间更少;
空间角度:浅拷贝花费内存更少;
效率角度:浅拷贝只拷贝顶层数据,一般情况下比深拷贝效率高。