深入理解Python深拷贝(deepcopy)、浅拷贝(copy)、等号拷贝----看了还不懂找我

         编程中难免会遇到copy(浅拷贝)与deepcopy(深拷贝)问题,一不小心就会掉进坑中了,或许很多人只知道有深浅拷贝,但是你又知道copy和"="拷贝有什么区别么?也许很少有人对二者的区别能讲出一二三吧!下面这篇文章就对深拷贝(deepcopy)、浅拷贝(copy)、等号拷贝进行深入的讲解。

        本文不是在网上抄袭别人的,而是本人通过研究学习后的自我总结,我想当你看完这篇文章后,1. 你会知道python的变量存储机制;2. 你会深浅拷贝以及等号拷贝的差异所在了;3. 你会理解为什么有的值改变,有的拷贝跟着变化,有的不会变化的根本原因所在了。当然如果本文有不正确的地方,欢迎大家批判指正。

首先在python中,什么是浅拷贝,什么是深拷贝呢?什么是等号拷贝(形如:bb = aa)呢?有谁能一句话解释清楚,他们的区别又在哪里?-----一定记住针只有对复杂对象才有区别意义!!!因为对于简单对象,他们是没有区别的!

主要是针对复杂结构对象,复杂结构对象就是嵌套两层及以上的子对象,比如:即列表中嵌套子列表,像[1, [2, 3]]这种结构

复杂对象中的深浅拷贝:

一句话解释(=)等号拷贝:当于对于电脑中某个文件夹新建了一个快捷图标,快捷图标永远和原文件是一致的。

一句话解释(copy)浅拷贝:相当于对于电脑中某个文件夹内部的所有子文件夹新建了快捷图标,放到新的文件夹中,所以内部子文件夹内数据会跟着原来文件的改变而改变。

一句话解释(deepcopy)深拷贝:相当于对于电脑中某个文件夹用u盘拷贝了一个备份。所以原来电脑中文件夹内文件改变时,u盘的文件是不会变化的。

官方解释:

  • 浅层拷贝 构造一个新的复合对象,然后(在尽可能的范围内)将原始对象中找到的对象的 引用 插入其中。

  • 深层拷贝构造一个新的复合对象,然后,递归地将在原始对象里找到的对象的 副本 插入其中

要弄清楚拷贝原理,首先应该弄清楚Python变量存储机制

Python中变量的存储机制

1. aa = 1的存储机制

当aa = 1 时,首先Python会在内存中新开辟一个空间存储数字“1”,然后将该内容的地址赋值给变量‘aa’。有点像如下图所示

2. bb = aa的复制机制

对于正常的“=” 赋值, 比如 bb = aa,则有

3. 形如or_list = [1, [2, 3]]的复制对象,他们的存储机制是如何的呢?

读者可以先自我思考一下,在本文的后面讲解中,会给出解释。

Python中复杂对象,等号拷贝,copy浅拷贝,deepcopy深拷贝机制

1. 三者拷贝后的ID差异

看以下代码初始列表为 or_list = [1, [2, 3]],分别进行"="拷贝,copy浅拷贝,deepcopy深拷贝

操作如下   

    eq_list = or_list
    sh_list = copy(or_list)
    de_list = deepcopy(or_list),

通过id可以看出三者和原来list的内存地址信息,如下:

    id(or_list)= 2269079198528
    id(eq_list)= 2269079198528
    id(sh_list)= 2269076677056
    id(de_list)= 2269076677632

【解释说明】:

1. 对于等号拷贝,没有新建新的内存空间,只是吧eq_list变量指向or_list变量相同的地址,通过id可以看出来,两个地址一样 id(or_list)= 2269079198528 和 id(eq_list)= 2269079198528

2. 对于copy拷贝,新创建了内存空间,并且把sh_list变量指向了该新地址。此时的新地址为id(sh_list)= 2269076677056

3. 对于deepcopy拷贝,也新创建了一个内存空间,并且整个List也指向了新的地址。此时的新地址为 id(de_list)= 2269076677632

2. 查看List的不同位置的id值

再通过id查看List内部位置存储的是什么,可以看到不同操作,不同位置就有差异了。

1. or_list[0]位置,所有的拷贝后的ID都是相同的,为什么呢?

2. or_list[1]位置,只有deepcopy拷贝不同,其它拷贝的id不变,这是为什么呢?

3. or_list[1][0]和or_list[1][1]的id为什么所有拷贝又都是一样的呢?

5.  ======列表第一个位置的ID值=======  
    id(or_list[0]) = 2269075013872
    id(eq_list[0]) = 2269075013872
    id(sh_list[0]) = 2269075013872
    id(de_list[0]) = 2269075013872

6.  ======列表第二个位置的ID值===========
    id(or_list[1]) = 2269076677312
    id(eq_list[1]) = 2269076677312
    id(sh_list[1]) = 2269076677312
    id(de_list[1]) = 2269077081216

6.1 ====列表第二个位置的子列表第一位置的ID值=====
    id(or_list[1][0]) = 2269075013904
    id(eq_list[1][0]) = 2269075013904
    id(sh_list[1][0]) = 2269075013904
    id(de_list[1][0]) = 2269075013904

6.2 ====列表第二个位置的子列表第二位置的ID值=====
    id(or_list[1][1]) = 2269075013936
    id(eq_list[1][1]) = 2269075013936
    id(sh_list[1][1]) = 2269075013936
    id(de_list[1][1]) = 2269075013936

【解释说明】

要解释清楚上面的为什么,就需要弄明白python中形如or_list = [1, [2, 3]]这样的存储原理。

首先,看形如or_list = [1, [2, 3]]的存储原理,在python中,形如or_list = [1, [2, 3]]的复制对象的存储原理为如下图所示:

其次,看不同拷贝的原理。

1. 对于等号拷贝,拷贝后实际上是将List的地址引用直接给了等号拷贝的变量,示意图如下所示

2. 对于copy拷贝,拷贝后的实际上新建了一个内存空间,一个用于存储List本身,内部子对象引用原来的地址,示意图如下:

3. 对于deepcopy拷贝,拷贝后的实际上新建了两个内存空间,一个用于存储List,另一个存储List的子对象,如果有更多,那么就会创建更多的内存空间。示意图如下:

最后,来回答上面的问题

1. or_list[0]位置,所有的拷贝后的ID都是相同的,为什么呢?

回答1:因为对于数字1,它是简单对象,不是复杂对象,不管怎么拷贝,他们都是相同的。

2. or_list[1]位置,只有deepcopy拷贝不同,其它拷贝的id不变,这是为什么呢?

回答2:对于List[1]位置,它是一个复杂对象,所以只有deepcopy新建了新的内存,所以只有它的id变化了。

3. or_list[1][0]和or_list[1][1]的id为什么所有拷贝又都是一样的呢?

回答3:因为这两个位置都是存储的是简单对象,所以在所有的拷贝之后,id是不会变的。

Python中复杂对象修改值后,等号拷贝,copy浅拷贝,deepcopy深拷贝变化情况详解

将所有的拷贝原理图放到一起,可以看到他们的区别和联系,如下图所示。以下是各个位置在发生改变后不同拷贝的变化机制详解。

1. 改变or_list[1][0]的值时

进行or_list[1][0] = 4操作,结果如下所示:

可以看出原始该位置存储的id是*176,初始列表该位置发生改变时,等号拷贝和copy浅拷贝也跟着发生了改变,但是deepcopy深拷贝没有发生变化。为什么会这样呢?改变的原理可以看下面的示意图。因为deepcopy是在List[1]位置就创建了新的内存空间,其它拷贝并没有,他们还是引用的原始列表相同的内存空间,所以才会跟着一起变化

======更改前:or列表的子列表第二个位置的id值===========
id(or_list[1][0])= 2269075013904
id(eq_list[1][0])= 2269075013904
id(sh_list[1][0])= 2269075013904
id(de_list[1][0])= 2269075013904

进行更改: or_list[1][0] = 4 
======更改后:or列表的子列表第二个位置的ID值===========
id(or_list[1][0])= 2269075013968
id(eq_list[1][0])= 2269075013968
id(sh_list[1][0])= 2269075013968
id(de_list[1][0])= 2269075013904
======更改后变量值===========
or_list_after_change =  [1, [4, 3]]
eq_list_after_change =  [1, [4, 3]]
sh_list_after_change =  [1, [4, 3]]
de_list_after_change =  [1, [2, 3]]

 2. 改变List[0]位置的值时

改变第一层的元素时,只有等号拷贝跟着改变了,是因为copy浅拷贝和deepcopy深拷贝都是新开辟了内存空间,存储List[0],List[1],所以当原始的List[0]发生改变时,这两个拷贝没有任何影响,具体原理查看以下示意图

======更改前:or列表的第一个位置的ID值===========
id(or_list[0])= 2269075013872
id(eq_list[0])= 2269075013872
id(sh_list[0])= 2269075013872
id(de_list[0])= 2269075013872

进行更改: or_list[0] = 5 
======更改后:or列表的第一个位置的ID值===========
id(or_list[0])= 2269075014000
id(eq_list[0])= 2269075014000
id(sh_list[0])= 2269075013872
id(de_list[0])= 2269075013872

======更改后变量值===========
or_list_after_change =  [5, [4, 3]]
eq_list_after_change =  [5, [4, 3]]
sh_list_after_change =  [1, [4, 3]]
de_list_after_change =  [1, [2, 3]]

 3. 改变List[1]位置的值时

原理同3改变List[0]位置的值。原理和代码参考以下代码是示意图

======更改前: or列表的第一个位置的ID值===========
id(or_list[1])= 2269076677312
id(eq_list[1])= 2269076677312
id(sh_list[1])= 2269076677312
id(de_list[1])= 2269077081216

进行更改: or_list[1] = {'1': 'a', 'b': 1}
======更改后: or列表的第一个位置的ID值===========
id(or_list[1])= 2269076306688
id(eq_list[1])= 2269076306688
id(sh_list[1])= 2269076677312
id(de_list[1])= 2269077081216

======更改后变量值===========
or_list_after_change =  [5, {'1': 'a', 'b': 10}]
eq_list_after_change =  [5, {'1': 'a', 'b': 10}]
sh_list_after_change =  [1, [4, 3]]
de_list_after_change =  [1, [2, 3]]

  • 40
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
深拷贝deep copy)和浅拷贝(shallow copy)是Python中关于对象复制的两个概念。 浅拷贝是指创建一个新的对象,其内容是原始对象的引用。也就是说,新对象与原始对象共享内存地址,对其中一个对象的修改会影响到另一个对象。在Python中,可以使用`copy`模块的`copy()`函数或者对象的`copy()`方法进行浅拷贝深拷贝则是创建一个新的对象,完全复制原始对象及其所有嵌套对象的内容。也就是说,新对象与原始对象完全独立,互不影响。在Python中,可以使用`copy`模块的`deepcopy()`函数或者对象的`deepcopy()`方法进行深拷贝。 下面是一个简单的示例代码来说明深拷贝浅拷贝的区别: ```python import copy # 原始对象 original_list = [1, 2, [3, 4]] print("原始对象:", original_list) # 浅拷贝 shallow_copy_list = copy.copy(original_list) print("浅拷贝对象:", shallow_copy_list) # 修改浅拷贝对象 shallow_copy_list[2][0] = 5 print("修改浅拷贝对象后,原始对象:", original_list) print("修改浅拷贝对象后,浅拷贝对象:", shallow_copy_list) # 深拷贝 deep_copy_list = copy.deepcopy(original_list) print("深拷贝对象:", deep_copy_list) # 修改深拷贝对象 deep_copy_list[2][1] = 6 print("修改深拷贝对象后,原始对象:", original_list) print("修改深拷贝对象后,深拷贝对象:", deep_copy_list) ``` 输出结果为: ``` 原始对象: [1, 2, [3, 4]] 浅拷贝对象: [1, 2, [3, 4]] 修改浅拷贝对象后,原始对象: [1, 2, [5, 4]] 修改浅拷贝对象后,浅拷贝对象: [1, 2, [5, 4]] 深拷贝对象: [1, 2, [3, 4]] 修改深拷贝对象后,原始对象: [1, 2, [5, 4]] 修改深拷贝对象后,深拷贝对象: [1, 2, [3, 6]] ``` 可以看到,对浅拷贝对象的修改会影响到原始对象,而对深拷贝对象的修改不会影响原始对象。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木瓜~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值