【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 copya = 1print(id(a), a)print(id(a), a)# 复制b = a# 浅拷贝c = copy.copy(a)# 深拷贝d = copy.deepcopy(a)a = 2print(id(a), a)print(id(b), b)print(id(c), c)print(id(d), d)结果:8791347356496 1 -- 变化前的a8791347356528 2 -- 变化后的a8791347356496 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 copya = [1, 2, [3, 4]]print(id(a), a)# 复制b = a# 浅拷贝c = copy.copy(a)# 深拷贝d = copy.deepcopy(a)b[0] = 9print(id(a), a)c[1] = 10c[2].append(11)print(id(a), a)d[2].append(12)print(id(a), a)结果:43241416 [1, 2, [3, 4]] -- 原对象a43241416 [9, 2, [3, 4]] -- 对复制进行改变后的a43241416 [9, 2, [3, 4, 11]] --对浅拷贝进行改变后的a43241416 [9, 2, [3, 4, 11]] -- 对深拷贝进行改变后的a

我想大家应该能理解 c[1]=10 为什么不改变原对象,而c[2].append(11) 能改变原对象的原理吧 ?

3. 总结

我觉得,理解复制/浅拷贝/深拷贝的关键还是要看对应外层元素/子层元素的地址有没有变化。地址变了,那就完全是新的对象;如果地址不变,那地址所对应的值变化,也就跟着变化了。另外,还要看原对象是可变对象还是不可变对象。

(0)

相关推荐