1.概念理解
- 赋值:
赋值其实就是简单的纯对象引用,并不会产生拷贝内容的操作
a = [1, 2, 3]
b = a
print(id(a), id(b)) # 1998317600136 1998317600136
print(a is b) # True
可以发现a, b是指向同一个地址的.因此除了 b这个名字之外,赋值操作并没有其他内存开销.
因此,修改a,b中任何一个都会修改对方的值.
- 浅拷贝:
浅拷贝会创建一个新对象,并且只拷贝原对象,但是子对象是对原对象内子对象的引用.
浅拷贝有三种形式:
import copy
a = [1, 2, 3, [2, 3]]
b = a[ : ] # 切片操作
b = list() # 工厂函数
b = copy.copy(a) # copy函数
print(a is b) # False
for i in range(0,4):
print(a[i] is b[i]) # True True True True
可以发现,a,b是不同的对象;
但是a,b中的相对应的子对象(指的是a,b列表中的元素)的地址是相同的;
因此浅拷贝之所以叫做浅拷贝,是因为只拷贝了原对象第一层,而其它层是对原对象的引用;
- 深拷贝
深拷贝相对浅拷贝而言,深拷贝拷贝的层级更深,包含所有层级,因此它的时间成本很高.
import copy
a = [1, 2, 3, [2, 3]]
c = copy.deepcopy(a) # 深拷贝唯一形式,copy函数
print(c is a) # Flase
深拷贝跟浅拷贝一样,会创建新的对象,且与原对象没有任何关联,并不会相互影响.
2.实例
import copy
a = [1, 2, 3, [2, 3]]
b = a
c = copy.copy(a) # copy函数
d = copy.deepcopy(a)
a.append(5) #操作1
a[3].append('hello') #操作2
print(id(a), id(b), id(c), id(d))
#1437646497288 1437646497288 1437646514632 1437646514440
print(a, b, c, d)
# [1, 2, 3, [2, 3, 'hello'], 5]
# [1, 2, 3, [2, 3, 'hello'], 5]
# [1, 2, 3, [2, 3, 'hello']]
# [1, 2, 3, [2, 3]]
解析:
- id解析
b是赋值产生的,因此与a是同一个对象,而深拷贝和浅拷贝都会产生新的对象; - 值解析
b 与 a 是同一个对象,因此a 改变,b也会跟随改变,始终保持一致;
c 是浅拷贝产生的只拷贝了本对象第一层,因此第一层不会改变,而子对象是引用a的,因此子对象会跟随a改变;
d 是深拷贝产生的,复制了原a的所有层级,不包含任何引用,与a没有任何关联,即使a的任何层级改变,d也不会发生任何改变.
3.关于深拷贝中拷贝原理的补充(拓展)
参考以下代码
import copy
a = [1, 2, 3, [4, [5, 6]]]
b = copy.deepcopy(a)
print((a[0]) is (b[0])) # True 不可变元素地址相同
print((a[1]) is (b[1])) # True 不可变元素地址相同
print((a[2]) is (b[2])) # True 不可变元素地址相同
print((a[3]) is (b[3])) # False 可变元素地址相同
print((a[3][0]) is (b[3][0])) # True 不可变元素地址相同
print((a[3][1]) is (b[3][1])) # False 可变元素地址相同
print((a[3][1][0]) is (b[3][1][0])) # True 不可变元素地址相同
print((a[3][1][1]) is (b[3][1][1])) # True 不可变元素地址相同
看到这是不是觉得有所疑惑?甚至颠覆认知?为什么深拷贝后产生的对象中的部分子对象与原对象中对应的子对象是相同的地址呢?(不是应该所有子对象都不同地址吗?)
这就要说到Python内存管理中的对象存储机制(此处不细说)。
Python对于整数(-5~256)和短字符串是缓存在内存当中的,这些对象在内存中只有一个,无论如何拷贝都不会产生复制对象,这是python节省内存空间的机制。
换句话说,在python中对于数字,(短)字符等原子类型是没有拷贝一说的,即使是深拷贝,产生的也是对原对象的引用.如果元组中含有原子类型,即使是深拷贝,得到的也是浅拷贝。