埋在浅拷贝里的坑

阅读本文大概需要 8 分钟

前言

面试的时候,深拷贝和浅拷贝是经常会被问到的问题,如果是你去面试,被问到它们之间有什么区别,你会怎么回答?

变量赋值

在理解深拷贝和浅拷贝之前,首先得知道一个问题:变量赋值。

在之前的文章里,介绍到 Python 中的变量其实是对象的引用,也就是变量只是指向了对象在内存中的所在地址。

如常见的:

>>> origin = [1,2,3,[4,5]]
>>> fz = origin

>>> origin == fz # == 比较的是对象本身
True

>>> origin is fz # is 比较的是对象所在地址
True

>>> id(origin)
1492148448

>>> id(fz)
1492148448

可以发现它们的地址是一样的,这里是因为变量origin只是对象[1,2,3,[4,5]]]的一个引用,相当于给它贴了一个标签,而fz = origin则是相当于给对象[1,2,3,[4,5]]又贴了一层标签,originfz指向的都是对象[1,2,3,[4,5]]的地址。

当对象本身发生变化时,不管对象引用是什么,由于他们都是指向对象,仅仅只是一个标签,故也会随之发生变化。

>>> a = [1,2,3]
>>> b = a

>>> a == b
True

>>> a is b
True

>>> a.append(4)
>>> b
[1, 2, 3, 4]

浅谈深拷贝和浅拷贝

无论在什么时候,对一些重要数据进行备份是一件很必要的事情,而怎么对数据进行拷贝,则成了所有语言中的一个重要知识点。

用到拷贝的时候需要导入 copy 模块

浅拷贝

>>> import copy

>>> origin = [1,2,3,[4,5]]
>>> cop = copy.copy(origin)

>>> cop
[1, 2, 3, [4, 5]]

>>> id(origin)
42761672

>>> id(cop)
42760584

>>> origin.append(6)
>>> origin
[1, 2, 3, [4, 5], 6]

>>> cop
[1, 2, 3, [4, 5]]

可以看到经过浅拷贝处理后,变量origincop所指向的对象的地址,已经不是同一个地址,说明浅拷贝已经单独开辟了一个内存空间,里面存放的是跟origin一样的内容,且在对origin进行append追加处理后,cop并没有发生变化,说明这两个已经是不同的对象,从而实现了对原对象的拷贝,并「独立」于原对象。

可是真的做到完全「独立」吗?

>>> origin[3].append(6)
>>> origin
[1, 2, 3, [4, 5, 6], 6]

>>> cop
[1, 2, 3, [4, 5, 6]]

可以看到,在对origin里的嵌套列表进行append追加操作时,进过浅拷贝后的cop里的嵌套列表也发生了变化,这是为什么呢?

原来所谓的「浅拷贝」只是对目标进行一层拷贝,如果目标里还嵌套了其他的对象,浅拷贝只是将它的引用进行拷贝,并没有将其指向的对象进行拷贝,所以当其发生变化时,浅拷贝后的对象自然也发生变化。

深拷贝

懂了浅拷贝就很容易理解深拷贝。

import copy

origin = [1,2,3,[4,5]]
dcop = copy.depcopy(origin)

>>> id(origin)
42698888

>>> id(dcop)
42698952

>>> origin.append(6)
>>> origin[3].append(6)

>>> dcop
[1, 2, 3,[4,5]]

可以发现深拷贝是单独开辟了一个内存空间,并且直接复制的对象内容,当原先被复制的对象发生改变时,并没有影响到经过深拷贝的对象,因为它们是分开的两个对象,互相并不影响。深拷贝实现了真正独立

赋值和拷贝的区别

赋值和拷贝在某些时候很相似,那他俩到底有什么区别呢?

>>> import copy

>>> origin = [1,2,3,[4,5]]

>>> fz = origin
>>> cop = copy.copy(origin)
>>> dcop = copy.deepcop(origin)

>>> fz == cop
True
>>> fz is cop
False
>>> fz == dcop
True
>>> fz is dcop
False

>>> id(origin)
42760392

>>> id(fz)
42760392

>>> id(cop)
42761672

>>> id(dcop)
42760584
>>> origin[3][1] = 'Python'

>>> origin
[1, 2, 3, [4, 'Python']]

>>> fz
[1, 2, 3, [4, 'Python']]

>>> cop
[1, 2, 3, [4, 'Python']]

>>> dcop
[1, 2, 3, [4, 5]]

赋值就好比一个电脑小白去电脑城里卖电脑,一些无良商家就会乘机宰你一顿,你去跟商家说,我要一台华硕的电脑,商家立马把电脑包装一撕,递给你。这时候你想到来之前在网上看的帖子,买电脑要验机!

你说不行,我怎么就能相信这就是华硕电脑呢?你得把电脑拆了,我检查检查,然后商家就把电脑拆了,让你看。你看着贴在零件上面一个个的标签,嗯~买对了,等你痛痛快快交完钱,回家上网一测,却发现里面的主板被换了,显卡也不是原装的了。。。

而拷贝就相当于是一个老手去买电脑,他一进电脑城,点名就要 mac,显卡要 GeForce2 MX,音响要 Odyssey,显示器要 Apple Studio Display。。。。老板知道你不好忽悠,也拿一台 mac 给你,也当场拆机。

copy 就像是一个混迹电脑吧多年的 14 级大佬,理论大于实践,只知道一些比较出名的型号是什么,却不知道里面装的究竟是阿猫还是阿狗,也就只能拆一层看看,再往下就看不懂了。

而 deepcopy 就像是一个电脑专家,亲自验机,一个一个撕开标签,看看标签下面的到底是什么,最后发现,嗯~是正品,然后跟老板来句,你这出厂日期有点远了啊,打个 8 折吧。。。。

也就是说赋值只是单纯的贴个新标签,而 copy 只能撕开了一层标签,但 deepcopy 是一层一层往下撕标签,直到最后一层。

埋在浅拷贝里面的坑

说到浅拷贝的时候,提到经过浅拷贝的处理的,是单独创建一个新的内存空间,里面存放着拷贝过来的对象,但真的是这样吗?

>>> import copy

>>> origin = (1,2,3,[4,5])
>>> cop = copy.copy(origin)
>>> dcop = copy.deepcopy(origin)

>>> id(origin)
38303208

>>> id(cop)
38303208

>>> id(dcop)
38845320

这里经过浅拷贝处理后cop,它的地址居然跟原对象是一样的!

原来浅拷贝在处理时,会根据要拷贝的对象类型进行不同处理,当对象类型是固定类型时,浅拷贝并不会开辟一个新的内存空间,而只是对原对象的一个引用,跟赋值操作是一样的,而当对象类型是可变类型时,则会开辟一个新的内存空间。

而深拷贝则不会有这种情况,无论原对象是什么类型,都是开辟一个新的内存空间!

结语

深拷贝和浅拷贝是一个很重要的知识点,简单的说:

  1. 浅拷贝会根据原对象的类型决定是否开辟一个新的内存空间,且若原对象是复杂结构,如列表里面嵌套列表,元组里面嵌套列表,则只会拷贝第一层对象,再往下只会拷贝对象引用。

  2. 深拷贝是直接开辟一个新的内存空间,并且遇到复杂结构会逐层向下,直到最后一层。

您的一次关注就足以让我感动!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值