哈喽大家好,我是鹏哥。
今天想聊聊的主题是 —— 浅拷贝/深拷贝。
~~~上课铃~~~
1
写在前面
浅拷贝、深拷贝的知识点,在python笔试和面试的过程中,经常被作为考题来考察应聘者的基本功。比如问你“请简单阐述下浅拷贝、深拷贝之间的差异”,或者给你一个变量分别进行复制、浅拷贝、深拷贝来求新值。
因此,我就简单把自己的理解记录下,希望对部分初学者有所帮助。
2
示例代码
在进行示例代码展示前,我们先理解下什么叫 复制、浅拷贝、深拷贝。
【直接赋值】:其实就是对象的引用(别名)。
【浅拷贝(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 = 1
print(id(a), a)
a = 2
print(id(a), a)
A = [1]
print(id(A), A)
A.append(2)
print(id(A), A)
A = [1,2]
print(id(A),A)
结果:
8791349191504 1
8791349191536 2
34759304 [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 copy
a = [1, 2, [3, 4]]
print(id(a), a)
# 复制
b = a
# 浅拷贝
c = copy.copy(a)
# 深拷贝
d = copy.deepcopy(a)
a[1] = 5
a[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]] -- 变化前的a
43241288 [1, 5, [3, 4, 6], 7] -- 变化后的a
43241288 [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个知识点里讲过了,这里不再复述。你可以理解成小伙子完全换了颗心脏,都不再是原先那个心脏了。(好吧,这个比方有点牵强。)
深拷贝:原对象的外层/内层元素地址都变化。这个就好理解了,前女友对你彻底死心,无论你怎么变都不要你了。
综上所述,复制相当于是无论什么条件都愿意跟着你结婚的好女孩;浅拷贝相当于有机会回到你身边的前女友,但要看你表现;深拷贝是完全对你死心的前女友。(和面试官可别这么说哈!)
3
补充点
如果对复制/浅拷贝/深拷贝的对象进行改变,那原对象会怎么变呢?
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)
能改变原对象的原理吧 ?
4
总结
我觉得,理解复制/浅拷贝/深拷贝的关键还是要看对应外层元素/子层元素的地址有没有变化。地址变了,那就完全是新的对象;如果地址不变,那地址所对应的值变化,也就跟着变化了。另外,还要看原对象是可变对象还是不可变对象。
~~~下课铃~~~
【往期热门文章】:
【Python成长之路】10行代码教你免费观看无广告版的《庆余年》腾讯视频
【Python成长之路】如何用python开发自己的iphone应用程序,并添加至siri指令
【Python成长之路】从 零做网站开发 -- 基于Flask和JQuery,实现表格管理平台
点击下方诗句,可以留言互动喔
【关注“鹏哥贼优秀”公众号,回复“python学习材料”,将会有python基础学习、机器学习、数据挖掘、高级编程教程等100G视频资料,及100+份python相关电子书免费赠送!】
扫描二维码
与鹏哥一起
学python吧!