深拷贝与浅拷贝
需要先理解什么是可变对象和不可变对象:可变对象与不可变对象的区别在于对象本身是否可变。
https://zhuanlan.zhihu.com/p/34395671 https://cloud.tencent.com/developer/article/1796730
python内置的一些类型中
- 可变对象:list dict set
- 不可变对象:tuple string int float bool
可变对象
>>> a = [1, 2, 3]
>>> a[1] = 4
>>> a
>[1, 4, 3]
不可变对象
>>> b = (1, 2, 3)
>>> b[1] = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
上面例子很直观地展现了,可变对象是可以直接被改变的,而不可变对象则不可以
地址问题
>>> a = [1, 2, 3]
>>> id(a)
2139167175368
>>> a[1] = 4
>>> id(a)
2139167175368
可变对象发生变化后,内存地址并没有发生改变
- 可变对象
a = [1,2,3]
id(a) # 1835066577160
b = a
id(a)==id(b) # True
a[0] = -1
id(a) # 1835066577160
id(a) == id(b) # True
a = b 他们俩的内存地址是一样的,a变,b也跟着变
需要注意的是:但是如果对a整体赋值,那a的地址会变,即a=[1,2,3]这样赋值的话地址会变,而单个赋值a[0]=4这样就不会变
对于可变对象 list这种进行操作时,相当于修改对象中某个属性,所以不会改变地址 python都是将对象的引用(内存地址)赋值给变量的
- 不可变对象
a = (1,2,3)
b = a
id(a) # 1835066350472
id(a) == id(b) # True
a = (4,5,6) # 发生了改变
id(a) # 1835066347752
id(b) # 1835066350472
对于不可变对象a,被赋予新值之后,地址也发生了变化。而b并没又发生变化,原地址内的内容也没变化
- 作为函数参数
def myfunc(l):
l.append(1) # 列表加1
print(l)
l = [1,2,3]
myfunc(l) # [1,2,3,1]
print(l) # [1,2,3,1]
可变对象l
送进函数中操作后,对函数外面的也起作用,不可变对象则相反。因为函数直接对可变对象的地址的值进行了修改。该操作对于类初始化传值有一样的效果
为什么会出现这种现象?
python中向函数传递参数只能是引用传递,表示把它的地址都传进去了,这才会带来上面的现象。有的编程语言允许值传递,即只是把值传进去,在里面另外找一个地址来放,这样就不会影响全局中的变量。
只有搞懂可变对象和不可变对象之后,才能理解下面的深拷贝与浅拷贝
深拷贝可以让变量随着一起变动,而浅拷贝只是引用了变量所在的地址,如果变量发生了改变,之前所引用的地址没有变。
浅拷贝
- 浅拷贝会创建一个新的容器对象(compound object)
- 对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址)
深拷贝
- 深拷贝和浅拷贝一样,都会创建一个新的容器对象(compound object)
- 和浅拷贝的不同点在于,深拷贝对于对象中的元素,深拷贝都会重新生成一个新的对象
- id(object):函数用于获取对象的内存地址,函数返回对象的唯一标识符,标识符是一个整数。
下面用代码来说明
import copy
a = [1,[2,3],'hello',{'key':'123'}] # 创建一个可变对象列表,其中有2个可变2个不可变
浅拷贝
b = copy.copy(a) # 浅拷贝
print("id(a):{}".format(id(a))) # 2558765150984
print("id(b):{}".format(id(b))) # 2558765151048
对可变对象进行浅拷贝,相当于创建一个新的容器对象(compound object),所以a和b的id是不一样的
但是嵌套之后的容器内部可变对象与不可变对象的内存关系是怎样的呢?
内部嵌套的为不可变对象
print('不可变对象')
print("id(a[0]):{}".format(id(a[0]))) # 140718221861280
print("id(b[0]):{}".format(id(b[0]))) # 140718221861280
可变对象内部嵌套了不可变对象,他们的内存地址是一样的
对于容器里面的元素对象,浅拷贝就只会使用原始元素的引用(内存地址),所以可以看到子元素的内存地址还是一样的
内部嵌套的是可变对象
print('可变对象')
print("id(a[1]):{}".format(id(a[1]))) # 2558765187272
print("id(b[1]):{}".format(id(b[1]))) # 2558765187272
他们两个的地址也是相同的,也就是会产生联动
深拷贝
c = copy.deepcopy(a)
print("id(a):{}".format(id(a))) # 2558765150984
print("id(c):{}".format(id(c))) # 2558765154184
在这里,深拷贝还是和浅拷贝情况相同,另外开辟一个内存地址
内部嵌套的为不可变对象
print('不可变对象')
print("id(a[0]):{}".format(id(a[0]))) # 140718221861280
print("id(c[0]):{}".format(id(c[0]))) # 140718221861280
和浅拷贝还是一样的,不可变对象所指向的内存地址仍然是一样的
内部嵌套的为可变对象
print('可变对象')
print("id(a[1]):{}".format(id(a[1]))) # 2558765187272
print("id(c[1]):{}".format(id(c[1]))) # 2558765152328
这里的情况就和浅拷贝就不一样了,深拷贝连带给可变对象子元素也分配了个新的内存地址,两者改变互不影响
赋值
d = a
print("id(a):{}".format(id(a))) # 2558765150984
print("id(d):{}".format(id(d))) # 2558765150984
赋值语句并没有生成新的容器,跟浅拷贝的区别在于外面的容器也是指向的a的内存地址,并没有生成新的容器,相当于给变量直接起个小名