Fork me on GitHub

拷贝有深浅,复制需谨慎

拷贝有深浅,复制需谨慎

在很多语言中都存在深浅拷贝两种拷贝数据的方式,Python中也不例外。本文中详细介绍了Python中的深浅拷贝的相关知识,文章的内容包含:

  • 对象、数据类型、引用
  • 赋值
  • 浅拷贝
  • 深拷贝

Python对象

我们经常听到:在Python中一切皆对象。其实,说的就是我们在Python中构造的任何数据类型都是一个对象,不管是数字、字符串、字典等常见的数据结构,还是函数,甚至是我们导入的模块等,Python都会把它当做是一个对象来处理。

所有的Python对象都拥有3个属性:

  • 身份
  • 类型

我们看一个简单的例子来理解上面的3个属性:

假设我们声明了一个name变量,通过id、type方法能够查看对象的身份和类型:

甚至是type本身也是一个对象,它也拥有自己的身份、类型:

Python中,万物皆对象

数据类型

在Python中,按照更新对象的方式,我们可以将对象分为2大类:可变数据类型不可变数据类型

  • 不可变数据类型:数值、字符串、布尔值。不可变对象就是对象的身份和值都不可变。新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。

  • 可变数据类型:列表、字典、集合。所谓的可变指的是可变对象的值可变,但是身份是不可变的。

1、首先我们看看不可变对象:

当我们定义了一个对象str1,给其赋值了“Python”,便会在内存中找到一个固定的内存地址来存放;但是,当我们将“Python”定义成另一个变量名的时候,我们发现:它在内存中的位置是不变的

也就是说,这个变量在计算机内存中的位置是不变的,只是换了一个名字来存放,来看3个实际的例子:

以上的例子说明:当我们对字符串、数值型、布尔值的数据改变变量名,并不会影响到数据在内存中的位置。

2、我们看看可变类型的例子:

列表、字典、集合都是一样的效果:

虽然是相同的数据,但是变量名字不同,内存中仍然会开辟新的内存地址来进行存放相同的数据,我们以字典为例:

引用

在Python语言中,每个对象都会在内存中申请开辟一块新的空间来保存对象;对象在内存中所在位置的地址称之为引用。

可以说,我们定义的变量名实际上就是对象的地址引用。引用实际上就是内存中的一个数字地址编号。在使用对象的时候,只要知道这个对象的地址,我们就可以操作这个对象。

因为这个数字地址不太容易记忆,所以我们使用变量名的形式来代替对象的数字地址。在Python中,变量就是地址的一种表示形式,并不会开辟新的存储空间。

我们通过一个例子来说明变量和变量指向的引用(内存地址)实际上就是一个东西:

赋值

相同数据,不同变量名

讨论完Python的对象、属性和引用3个重要的概念之后,在正式介绍深浅拷贝之前,我们先讨论Python中的赋值

在Python中,每次赋值都会开辟新的内存地址来存放数据,比如我们同时存放一个列表[1,2,3],即使数据是相同的,但是内存地址却不同:

其实就是两个不同的变量,只是恰好它们存放了相同的数据而已,但是存放的地址是不同的。

我们给v1列表追加了一个元素,发现它的内存地址是不变的,当然v2肯定是不变的:

一个变量多次赋值

如果我们对一个变量多次赋值,其内存是会变化的:

变量赋值

将一个变量赋值给另一个变量,其实它们就是同一个对象:数据相同,在内存中的地址也相同:

当我们给V1追加一个元素,V2也会同时变化:

实际上它们就是同一个对象!!!!

嵌套赋值

如果是列表中嵌套着另外的列表,那么当改变其中一个列表的时候,另一个列表中的也会随着改变:

原始数据信息:

当我们给v1追加了新元素之后:

总结:赋值其实就是将一个对象的地址赋值给一个变量,使得变量指向该内存地址。

浅拷贝

在Python中进行拷贝之前,我们需要导入模块:

1
import copy

⚠️浅拷贝只是拷贝数据的第一层,不会拷贝子对象

不可变类型的浅拷贝

如果只是针对不可变的数据类型(字符串、数值型、布尔值),浅拷贝的对象和原数据对象是相同的内存地址:

从上面的结果中我们可以看出来:针对不可变类型的浅拷贝,只是换了一个名字,对象在内存中的地址其实是不变的。

image-20201115225938833

可变类型的浅拷贝

1、首先我们讨论的是不存在嵌套类型的可变类型数据(列表、字典、集合)

image-20201115232303901

从上面的例子看出来:

  • 列表本身的浅拷贝对象的地址和原对象的地址是不同的,因为列表是可变数据类型
  • 列表中的元素(第1个元素为例)和浅拷贝对象中的第一个元素的地址是相同的,因为元素本身是数值型,是不可变的

通过一个图形来说明这个关系:

字典中也存在相同的情况:字典本身的内存地址不同,但是里面的键、值的内存地址是相同的,因为键值都是不可变类型的数据。

2、如果可变类型的数据中存在嵌套的结构

从上面的两个例子中我们可以看出来:

在可变类型的数据中,如果存在嵌套的结构类型,浅拷贝只复制最外层的数据,导致内存地址发生变化,里面数据的内存地址不会变

深拷贝

深拷贝不同于浅拷贝的是:深拷贝会拷贝所有的可变数据类型,包含嵌套的数据中的可变数据

深拷贝是变量对应的值复制到新的内存地址中,而不是复制数据对应的内存地址

不可变类型的深拷贝

关于不可变类型的深浅拷贝,其效果是相同的,具体看下面的例子:

我们得出一个结论:

针对不可变数据类型的深浅拷贝,其结果是相同的

可变类型的深拷贝

1、首先我们讨论的是不存在嵌套的情况:

针对列表数据

针对字典数据

我们可以得出结论:

1、 深拷贝对最外层数据是只拷贝数据,会开辟新的内存地址来存放数据

2、深拷贝对里面的不可变数据类型直接复制数据和地址,和可变类型的浅拷贝是相同的效果

2、我们讨论存在嵌套类型的深拷贝(以列表为例)

结论1:对整个存在嵌套类型的数据进行深浅拷贝都会发生内存的变化,因为数据本身是可变的

结论2:我们查看第一个元素1的内存地址,发生三者是相同的,因为1是属于数值型,是不可变类型

结论3:我们查看第三个元素即里面嵌套列表的内存,发现只有深拷贝是不同的,因为这个嵌套的列表是可变数据类型,深拷贝在拷贝了最外层之后还会继续拷贝子层级的可变类型

结论4:我们查看嵌套列表中的元素的内存地址,发现它们是相同的,因为元素是数值型,是不可变的,不受拷贝的影响

总结

通过大量的例子,我们得出结论:

  • 浅拷贝只会拷贝最外层的数据
  • 深拷贝会拷贝所有层级的可变类型数据

本文标题:拷贝有深浅,复制需谨慎

发布时间:2020年11月15日 - 23:11

原始链接:http://www.renpeter.cn/2020/11/15/%E6%8B%B7%E8%B4%9D%E6%9C%89%E6%B7%B1%E6%B5%85%EF%BC%8C%E5%A4%8D%E5%88%B6%E9%9C%80%E8%B0%A8%E6%85%8E.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea