copy 命令_deep copy

copy 命令_deep copyPython等号拷贝、深拷贝、浅拷贝的区别和深入理解,以及深入理解python变量存储机制

         编程中难免会遇到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’。有点像如下图所示

copy 命令_deep copy

2. bb = aa的复制机制

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

copy 命令_deep copy

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]]的复制对象的存储原理为如下图所示:

copy 命令_deep copy

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

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

copy 命令_deep copy

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

copy 命令_deep copy

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

最后,来回答上面的问题

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深拷贝变化情况详解

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

copy 命令_deep copy

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]]

copy 命令_deep copy

 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]]

copy 命令_deep copy

 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]]

copy 命令_deep copy

今天的文章copy 命令_deep copy分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/79567.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注