深拷贝和浅拷贝的区别——“=“赋值、copy和deepcopy的详解

2. “=“赋值、copy和deepcopy的详解

一定记住针只有对复杂对象(列表里面有列表嵌套)才有区别意义!!!因为对于简单对象,他们是没有区别的!

基本原理:

“=”赋值 没有新建新的内存空间,只是把赋值变量指向原来变量相同的地址,通过id可以看出来,两个地址一模一样 。

浅拷贝不会产生独立对象,约等于赋值,只是对原有数据块打上新标签,其中一个标签改变,数据块就会变化。浅拷贝会对其中的子对象进行拷贝如果对子对象进行修改,拷贝结果也会随着修改,但是对拷贝对象里面的元素修改,原对象不会发生改变。(后面有例子)

deepcopy是真正意义上的复制,深拷贝,被复制对象完全复制一遍作为独立的新个体,新开辟一块空间

一句话形象解释:

复杂对象中的深浅拷贝:

一句话解释(=)等号拷贝: 当于对于电脑中某个文件夹新建了一个快捷图标,快捷图标永远和原文件是一致的。(对于等号拷贝,拷贝后实际上是将List的地址引用直接给了等号拷贝的变量)

一句话解释(copy)浅拷贝: 相当于对于电脑中某个文件夹内部的所有子文件夹新建了快捷图标,放到新的文件夹中,所以内部子文件夹内数据会跟着原来文件的改变而改变,但是总的文件夹的图标是发生了变化的。(对于copy拷贝,拷贝后的实际上新建了一个内存空间,一个用于存储List本身,内部子对象引用原来的地址,因此如果对内部子对象更改,那么copy后的数据也会发生更改)

一句话解释(deepcopy)深拷贝: 相当于对于电脑中某个文件夹用u盘拷贝了一个备份。所以原来电脑中文件夹内文件改变时,u盘的文件是不会变化的。(对于deepcopy拷贝,拷贝后的实际上新建了两个内存空间,一个用于存储List,另一个存储List的子对象)

官方解释:


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

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

专业解释:

  • 复制的对象中无复杂子对象,即列表中不嵌套列表,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。
  • 复制的对象中有复杂子对象 (例如列表中的一个子元素是一个列表)如果改变复杂子对象的值(列表中的值)会影响浅复制的值。
  • 对于 不可变类型(元组、数值,字符串等) 为浅拷贝,对象的id值与浅复制原来的值相同
  • 对于 可变类型(列表、字典等) 为 深拷贝

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

python 中标识一个对象唯一身份的是: 对象的id(内存地址),对象类型,对象值, 而深浅拷贝和=赋值 的区别跟python的变量存储机制有一定关系

Python中变量的存储机制

在这里插入图片描述

具体原理可参考这篇文章,通俗易懂yyds_深入理解Python深拷贝(deepcopy)、浅拷贝(copy)、等号拷贝

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(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内部位置存储的是什么,可以看到不同操作,不同位置就有差异了。

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

主要几个问题:

  1. or_list[0]位置,所有的拷贝后的ID都是相同的,为什么呢?
  • 回答1:因为对于数字1,它是简单对象,不是复杂对象,不管怎么拷贝,他们都是相同的。
  1. or_list[1]位置,只有deepcopy拷贝不同,其它拷贝的id不变,这是为什么呢?
  • 回答2:对于List[1]位置,它是一个复杂对象,所以只有deepcopy新建了新的内存,所以只有它的id变化了。
  1. or_list[1][0]和or_list[1][1]的id为什么所有拷贝又都是一样的呢?
  • 回答3:因为这两个位置都是存储的是简单对象,所以在所有的拷贝之后,id是不会变的。
  1. or_list[1][1]、eq_list[1][1]、sh_list[1][1]、de_list[1][1]为什么又是一样的id呢?
  • 因为都是简单对象,所以id没有区别。

实例分析:

import copy
a = [1,2,3,[4,5],1]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

a.append(9)
a[3].append(6)

print(a) 	#[1, 2, 3, [4, 5, 6], 1, 9]
print(b)	#[1, 2, 3, [4, 5, 6], 1, 9]   # 等号赋值二者公用一个存储空间,因此a,b变化相同
print(c)	#[1, 2, 3, [4, 5, 6], 1]  # copy赋值,内部子对象公用存储空间,因此对内部子对象的操作变化相同,但是如果对整个list操作(如a.append(),a.remove()),则变化不同。
print(d)	#[1, 2, 3, [4, 5], 1]   # deepcopy()赋值,独立开辟了一片存储空间,操作较原变量独立

print(id(a))	#4594148288
print(id(b))	#4594148288   # = 赋值 实际上将地址引用直接给新的变量
print(id(c))	#4594455328   # copy赋值,实际上新建了一个内存空间,一个用于存储List本身,内部子对象引用原来的地址
print(id(d))	#4592688496   # deepcopy()赋值 实际上新建了两个内存空间,一个用于存储List,另一个存储List的子对象,如果有更多,那么就会创建更多的内存空间


x = 'Hello World'
y = x
z = copy.copy(x)
w = copy.deepcopy(x)
# 简单对象没有区别
print(id(x)) #4617118576
print(id(y)) #4617118576
print(id(z)) #4617118576
print(id(w)) #4617118576

拓展–C++ 里面的深浅拷贝:

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存(分支)。

  • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

  • 如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址里的值,就会影响到另一个对象。

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)

  • 拷贝第一层级的对象属性或数组元素

  • 递归拷贝所有层级的对象属性和数组元素

  • 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
    在这里插入图片描述

参考文献:

python中copy和deepcopy详解

yyds_深入理解Python深拷贝(deepcopy)、浅拷贝(copy)、等号拷贝

猜你喜欢

转载自blog.csdn.net/m0_63669388/article/details/132706327