关于Python中变量的赋值、浅拷贝和深拷贝,看了好几篇文章都没有理解得很清晰,所以自己通过一些简单的实验验证一下。
要想弄清楚赋值、浅拷贝和深拷贝的作用,首先要理解python中关于引用和对象的概念。以下面简单的代码为例:
a = 2
b = 2
上面的代码中,2是一个对象,变量a和变量b都是2的一个引用(地址)。即,在python中,新建一个变量都是在为某个对象新增一个引用;如果多个变量映射的对象的内容完全相同,则它们实际映射的是同一个对象。
在Python中,对象根据其内容是否可变分为可变对象和不可变对象:
- 可变对象:列表、字典
- 不可变对象:数值(int/float)、字符串、元组
下面通过实验来具体认识赋值、浅拷贝和深拷贝。分别验证它们对不可变对象、简单可变对象(其中的子元素都是不可变对象)和复杂可变对象(子元素中含有可变对象)的作用。
对不可变对象:
import copy
a = 1
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(id(a), id(b), id(c), id(d)) # 1690462992 1690462992 1690462992 1690462992
a = 2
print(a, b, c, d) # 2 1 1 1
print(id(a), id(b), id(c), id(d)) # 1690463024 1690462992 1690462992 1690462992
对于不可变对象,赋值、浅拷贝和深拷贝都不创造新对象,只是增加了原有对象的引用。改变a的值,就是将变量a映射到了新的对象2上,故a的地址发生改变;同时,对象1的引用计数减1。变量b、c、d仍然是对象1的引用,值均不变。
对简单可变对象:
import copy
a = [1, 1]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(id(a), id(b), id(c), id(d)) # 2735941969160 2735941969160 2735941969416 2735941970568
print(id(a[0]), id(a[1])) # 1690462992 1690462992
print(id(b[0]), id(b[1])) # 1690462992 1690462992
print(id(c[0]), id(c[1])) # 1690462992 1690462992
print(id(d[0]), id(d[1])) # 1690462992 1690462992
a[0] = 2
print(id(a), id(b), id(c), id(d)) # 2735941969160 2735941969160 2735941969416 2735941970568
print(id(a[0]), id(a[1])) # 1690463024 1690462992
print(id(b[0]), id(b[1])) # 1690463024 1690462992
print(id(c[0]), id(c[1])) # 1690462992 1690462992
print(id(d[0]), id(d[1])) # 1690462992 1690462992
对于简单可变对象,赋值仍然只是增加原对象的引用;但此时浅拷贝和深拷贝都单独开辟内存创造了新对象,但新对象中的元素1是已经存在的对象,故该子元素仍是映射到最开始的1对象 (注意id(a)是list对象的地址,id(a[i])是list中第i个元素映射的对象的地址)。改变a[0],由于b相当于只是a的别名,故b随之改变,但c、d都映射到了新对象,故不受影响。
对复杂可变对象:
import copy
a = [1, [2, 2]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(id(a), id(b), id(c), id(d)) # 2735941970440 2735941970440 2735941969160 2735941969416
print(id(a[0]), id(a[1])) # 1690462992 2735941970504
print(id(b[0]), id(b[1])) # 1690462992 2735941970504
print(id(c[0]), id(c[1])) # 1690462992 2735941970504
print(id(d[0]), id(d[1])) # 1690462992 2735941970376
a[1].append(2)
print(id(a), id(b), id(c), id(d)) # 2735941970440 2735941970440 2735941969160 2735941969416
print(id(a[0]), id(a[1])) # 1690462992 2735941970504
print(id(b[0]), id(b[1])) # 1690462992 2735941970504
print(id(c[0]), id(c[1])) # 1690462992 2735941970504
print(id(d[0]), id(d[1])) # 1690462992 2735941970376
对于复杂可变对象,赋值增加原对象的引用,浅拷贝和深拷贝都映射到新对象;不同的是,浅拷贝不会为子元素中可变对象开辟新的内存,而深拷贝会。故改变a中可变对象后,b、c都会随之改变,d不受影响。
总结:
- 赋值对所有对象来说,作用都是增加对象的引用;
- 浅拷贝对于不可变对象:增加对象引用;对于简单可变对象:创造新对象;对于复杂可变对象:为顶层可变对象创造新对象,对子可变对象只是增加原对象引用。
- 深拷贝对于不可变对象:增加对象引用;对于可变对象:为所有层次的可变对象创造新对象。