Python中深浅拷贝的区别详细分析

 更新时间:2023年06月13日 15:30:22   作者:某学酥 Kevin  
深拷贝和浅拷贝都是对原对象的拷贝,都会生成一个看起来相同的对象,下面这篇文章主要给大家介绍了关于Python中深浅拷贝的区别的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

简而言之:

深浅拷贝的区别关键在于拷贝的对象类型是否可变。

我们可以总结出以下三条规则:

  • 对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝
  • 而对于不可变对象来说,深浅拷贝都不会开辟新地址,只是建立引用关联
    ,等价于赋值
  • 对于复合对象来说,浅拷贝只考虑最外层的类型,复合类型数据中的元
    素仍为引用关系;而深拷贝对复合对象递归应用前两条规则

背后的逻辑也很容易理解,我们可以在 Python 的官方文档里找到如下解释:

Python 的赋值语句不复制对象,而是创建目标和对象的绑定关系。对于自身可变,或包含可变项的集合,有时要生成副本用于改变操作,而不必改变原始对象。

  • 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);
  • 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。

下面我们通过对不同类型的对象进行深浅拷贝来逐条说明上述规则:

不可变对象

以元组(tuple)为例:

import copy
 
tup1 = (991, "abc")
tup2 = copy.copy(tup1) # 浅拷贝
# tup2 = tup1 # 在这个例子里浅拷贝等价于赋值
 
print(id(tup1))
print(id(tup2))
 
print(tup1 == tup2)
print(tup1 is tup2)
 
# 2457279675264
# 2457279675264
# True
# True
 
tup2 = copy.deepcopy(tup1) # 深拷贝
 
print(id(tup1))
print(id(tup2))
 
print(tup1 == tup2)
print(tup1 is tup2)
 
# 1291830377344
# 1291830377344
# True
# True

我们可以看到,对于不可变对象,深拷贝还是浅拷贝都不会为我们对象建立真正的副本,tup2 和 tup1的地址完全相同,实际上引用的是同一个对象。

可变对象

以列表(list)为例: 

import copy
 
lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.copy(lis1) # 浅拷贝
 
print(id(lis1))
print(id(lis2))
 
print(lis1 == lis2)
print(lis1 is lis2)
 
# 2491304912896
# 2491304912960
# True
# False
 
lis2 = copy.deepcopy(lis1) # 深拷贝
print(id(lis1))
print(id(lis2))
 
print(lis1 == lis2)
print(lis1 is lis2)
 
# 2841088174144
# 2841088174208
# True
# False

可以看到,对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝。

复合对象

其实上面例子中的列表同时还是一个复合对象(即包含其他对象的对象)

对于复合对象来说,浅拷贝只考虑最外层的类型,复合类型数据中的元素仍为引用关系。深拷贝对复合对象递归应用前两条规则。

import copy
tup3 = (991, "abc", [])
tup4 = copy.copy(tup3) # 浅拷贝
print(tup3 is tup4) # True
print(tup3[-1] is tup4[-1]) # True
lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.copy(lis1) # 浅拷贝
print(lis1 is lis2) # False
# 虽然 lis1 和 lis2 的地址不同,但其中的每个元素都各自指向同一个对象
print(lis1[0] is lis2[0]) # True
print(lis1[1] is lis2[1]) # True
print(lis1[2] is lis2[2]) # True
print(lis1[3] is lis2[3]) # True
print(lis1[4] is lis2[4]) # True
print(lis1[5] is lis2[5]) # True
print(lis1[6] is lis2[6]) # True
print(lis1[7] is lis2[7]) # True

可以看到对于复合对象,其最外层的逻辑和前文提到的相同,即可变对象开辟新地址,不可变对象不开辟新地址。但复合对象内的元素全部只是建立引用关联,地址相同。

而深拷贝还需确认复合对象中的所有元素是否都不可变然后在对元素递归应用前两条规则。只要复合对象本身是可变的或者其中存在可变对象,则都会完成拷贝。

import copy
lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.deepcopy(lis1) # 深拷贝
print(lis1 is lis2) # False,列表是可变对象,深复制后地址改变
print(lis1[0] is lis2[0]) # True,索引0是整数,不可变,地址不变
print(lis1[3] is lis2[3]) # False, 索引3是列表,可变,地址改变
tup3 = (991, "abc", [])
tup4 = copy.deepcopy(tup3) # 深拷贝
print(tup3 is tup4) # False,虽然 tup3 是不可变对象,但其内部存在可变对象,所以深复制后地址仍然改变
print(tup3[0] is tup4[0]) # True,索引 0 是整数,不可变,地址不变
print(tup3[1] is tup4[1]) # True,索引 3 是字符串,不可变,地址不变
print(tup3[2] is tup4[2]) # False,索引 3 是列表,可变,地址改变

参考:

copy — Shallow and deep copy operations — Python 3.10.7 documentation(Python3 文档)

补充:下面解释可变类型和不可变类型的嵌套使用

(1)可变类型:

浅拷贝和深拷贝只要最外层是可变类型都会生成新的对象

  • [] 或者{}, 浅拷贝和深拷贝都会生成新的对象
  • [[],[]]列表的嵌套,可变类型嵌套了可变类型,浅拷贝:只拷贝最外层,会生成新的对象,内层是引用。深拷贝:外层和内层都会进行拷贝,都是全新的对象,都有独立的存储空间
  • [(),()] 外层可变,内层不可变,浅拷贝:只拷贝最外层,会生成新的对象,内层是引用。深拷贝:外层和内层都会进行拷贝,外层会生成新对象,但是由于内层是不可变类型,所以内层依然是引用
  • [(),[]] 外层可变,内层有一个是可变,浅拷贝:只拷贝最外层,会生成新的对象,内层是引用。深拷贝:外层和内层都会进行拷贝,外层会生成新对象,内层可变对象会生成新对象,内层不可变对象是引用

(2)不可变类型:

最外层是不可变类型,浅拷贝就一定是引用

  • Number、字符串或者(), 浅拷贝和深拷贝都是引用
  • ([],[]), copy浅拷贝:只会拷贝最外层,内层只是引用,但是最外层是不可变,拷贝之后毫无意义,仅仅是引用关系。deepcopy:从外层到内层都会拷贝,内层是可变,为了达到和原来的数据完全隔离,会生成全新的对象
  • ((),()) 完全不可变,拷贝了之后如果生成新的数据也无法修改,所以不管深拷贝还是浅拷贝都是引用
  • ((),[]) 外层不可变,但是内层有一个是可变,copy依然是引用,deepcopy,会生成新的对象,内层的不可变类型是引用,可变类型会生成新的对象。

总结

到此这篇关于Python中深浅拷贝的区别的文章就介绍到这了,更多相关Python深浅拷贝的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Tensorflow: 从checkpoint文件中读取tensor方式

    Tensorflow: 从checkpoint文件中读取tensor方式

    今天小编就为大家分享一篇Tensorflow: 从checkpoint文件中读取tensor方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-02-02
  • Python使用asyncio包实现异步编程方式

    Python使用asyncio包实现异步编程方式

    这篇文章主要介绍了Python使用asyncio包实现异步编程方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • 把pandas转换int型为str型的方法

    把pandas转换int型为str型的方法

    今天小编就为大家分享一篇把pandas转换int型为str型的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-01-01
  • Python实现双X轴双Y轴绘图的示例详解

    Python实现双X轴双Y轴绘图的示例详解

    这篇文章主要介绍了如何利用fig.add_subplot和axes.twinx().twiny()方法实现双X轴双Y轴绘图,文中的示例代码讲解详细,快跟随小编一起动手尝试一下吧
    2022-04-04
  • NumPy矩阵乘法的实现

    NumPy矩阵乘法的实现

    本文主要介绍了NumPy矩阵乘法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Pytest allure 命令行参数的使用

    Pytest allure 命令行参数的使用

    这篇文章主要介绍了Pytest allure 命令行参数的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 在pytorch 中计算精度、回归率、F1 score等指标的实例

    在pytorch 中计算精度、回归率、F1 score等指标的实例

    今天小编就为大家分享一篇在pytorch 中计算精度、回归率、F1 score等指标的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-01-01
  • Pyinstaller 打包exe教程及问题解决

    Pyinstaller 打包exe教程及问题解决

    这篇文章主要介绍了Pyinstaller 打包exe教程及问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • Python使用Flask-SQLAlchemy连接数据库操作示例

    Python使用Flask-SQLAlchemy连接数据库操作示例

    这篇文章主要介绍了Python使用Flask-SQLAlchemy连接数据库操作,简单介绍了flask、Mysql-Python以及Flask-SQLAlchemy的安装方法,并结合实例形式分析了基于Flask-SQLAlchemy的数据库连接相关操作技巧,需要的朋友可以参考下
    2018-08-08
  • Sublime开发python程序的示例代码

    Sublime开发python程序的示例代码

    本篇文章主要介绍了Sublime开发python程序的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01

最新评论