【Python 成长之路】快速理解复制、浅拷贝、深拷贝
1. 示例代码
在进行示例代码展示前,我们先理解下什么叫 复制、浅拷贝、深拷贝。
【直接赋值】:其实就是对象的引用(别名)。
【浅拷贝 (copy)】:拷贝父对象,不会拷贝对象的内部的子对象。
【深拷贝 (deepcopy)】:copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
好吧,反正我开始了解这些时对这 么官方的介绍是看不懂的,完全不知道是 什么意思。
Talk is cheap, Show me the code!
a = 1
# 复制
b = a
# 浅拷贝
c = copy.copy(a)
# 深拷贝
d = copy.deepcopy(a)
在进行介绍复制、浅拷贝、深拷贝前,我们还要了解下可变对象 (dict/list/set)、不可变对象 (int/str/float/tuple) 。
【知识点 1】可变对象、不可变对象
【可变对象】:当有需要改变对象内部的值的时候,这个对象的 id 不发生变化。
【不可变对象】:当有需要改变对象内部的值的时候,这个对象的 id 会发生变化。
a = 1print(id(a), a)a = 2print(id(a), a)A = [1]print(id(A), A)A.append(2)print(id(A), A)A = [1,2]print(id(A),A)结果:8791349191504 18791349191536 234759304 [1]34759304 [1, 2]34759368 [1, 2]
从上述代码可以看到,对不可变对象a进行值改变时,a 的地址也发生了变化;但对可变对象A进行值改变时,内存地址是不变的,仍然是 34759304。(这里注意, A=[1,2] 是重新赋值,所以是新地址,可以理解成是 A ,已不再是 A )
【知识点 2】对不可变对象进行复制、浅/深拷贝
对不可变对象,复制/浅拷贝/深拷贝都是引用原对象的内存地址。
import copy
a = 1
print(id(a), a)print(id(a), a)
# 复制
b = a
# 浅拷贝
c = copy.copy(a)
# 深拷贝
d = copy.deepcopy(a)
a = 2
print(id(a), a)
print(id(b), b)
print(id(c), c)
print(id(d), d)
结果:
8791347356496 1 -- 变化前的a
8791347356528 2 -- 变化后的a
8791347356496 1 -- 复制
8791347356496 1 -- 浅拷贝
8791347356496 1 -- 深拷贝
因此,对于不可变对象,如果原对象发生什么变化,复制/浅拷贝/深拷贝都不会跟着变。就像对于一个又没有上进心又穷的小伙子,无论什么女生都不会跟着他过一生,这男的要怎么样就怎么样吧,没人管他。
【知识点 3】对可变对象进行复制、浅/深拷贝
import copya = [1, 2, [3, 4]]print(id(a), a)# 复制b = a# 浅拷贝c = copy.copy(a)# 深拷贝d = copy.deepcopy(a)a[1] = 5a[2].append(6)a.append(7)print(id(a), a)print(id(b), b)print(id(c), c)print(id(d), d)结果:43241288 [1, 2, [3, 4]] -- 变化前的a43241288 [1, 5, [3, 4, 6], 7] -- 变化后的a43241288 [1, 5, [3, 4, 6], 7] -- 复制43311048 [1, 2, [3, 4, 6]] -- 浅拷贝43310984 [1, 2, [3, 4]] -- 深拷贝
复制:原对象怎么变,我跟着变。另外,这里在可以发现对于可变对象,a 变化前后的内存地址是不变的;就像老人们常说的“嫁鸡随鸡,嫁狗随狗”。
浅拷贝:原对象的外层元素地址变化,内层元素的地址不变。因为浅拷贝是会拷贝原对象的父对象,因此只有外层元素的内存地址发生了变化,因此通过 a.append(7) 改变外层元素,即外层的列表时,对浅拷贝是不影响的;
那这里你会肯定会问,为什么 a[1]=5 不变,而 a[2].append(6) 又发生了变化?因为 a[1] 是不可变对象,a[2] 是可变对象。前面已经提过了,对于不可变对象是不会跟着变的。对于可变对象的子对象变了,浅拷贝是会跟着变的。
就像上面提到的又没有上进心又穷的小伙子,如果只是改变外观(外层元素地址变了)或者变得有钱(穷属性是不可变对象),是不会挽回前女友(浅拷贝)的心,只有改变了上进心(上进心属性是可变对象),前女友才会回心转意而发生变化。
当然如果 a[2] 是通过 a[2]=[3,4,6] 来改变的话,浅拷贝是不会跟着变化的。原理已经在第1个知识点里讲过了,这里不再复述。你可以理解成小伙子完全换了颗心脏,都不再是原先那个心脏了。(好吧,这个比方有点牵强。)
深拷贝:原对象的外层/内层元素地址都变化。这个就好理解了,前女友对你彻底死心,无论你怎么变都不要你了。
综上所述,复制相当于是无论什么条件都愿意跟着你结婚的好女孩;浅拷贝相当于有机会回到你身边的前女友,但要看你表现;深拷贝是完全对你死心的前女友。(和面试官可别这么说哈!图片)
2. 补充点
如果对复制/浅拷贝/深拷贝的对象进行改变,那原对象会怎么变呢?
import copy
a = [1, 2, [3, 4]]
print(id(a), a)
# 复制
b = a
# 浅拷贝
c = copy.copy(a)
# 深拷贝
d = copy.deepcopy(a)
b[0] = 9
print(id(a), a)
c[1] = 10
c[2].append(11)
print(id(a), a)
d[2].append(12)
print(id(a), a)
结果:
43241416 [1, 2, [3, 4]] -- 原对象a
43241416 [9, 2, [3, 4]] -- 对复制进行改变后的a
43241416 [9, 2, [3, 4, 11]] --对浅拷贝进行改变后的a
43241416 [9, 2, [3, 4, 11]] -- 对深拷贝进行改变后的a
我想大家应该能理解 c[1]=10 为什么不改变原对象,而c[2].append(11) 能改变原对象的原理吧 ?
3. 总结
我觉得,理解复制/浅拷贝/深拷贝的关键还是要看对应外层元素/子层元素的地址有没有变化。地址变了,那就完全是新的对象;如果地址不变,那地址所对应的值变化,也就跟着变化了。另外,还要看原对象是可变对象还是不可变对象。