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深浅拷贝的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python使用pickle进行序列化和反序列化的示例代码

    Python使用pickle进行序列化和反序列化的示例代码

    这篇文章主要介绍了Python使用pickle进行序列化和反序列化,帮助大家更好的理解和使用python的pickle库,感兴趣的朋友可以了解下
    2020-09-09
  • python3操作redis实现List列表实例

    python3操作redis实现List列表实例

    本文主要介绍了python3操作redis实现List列表实例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 利用jupyter网页版本进行python函数查询方式

    利用jupyter网页版本进行python函数查询方式

    这篇文章主要介绍了利用jupyter网页版本进行python函数查询方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04
  • Cython处理C字符串的示例详解

    Cython处理C字符串的示例详解

    如果你在使用 Cython 加速 Python 时遇到了瓶颈,但还希望更进一步,那么可以考虑将数据的类型替换成 C 的类型,所以本文为大家介绍了Cython处理C字符串的方法,希望对大家有所帮助
    2023-01-01
  • 举例讲解Linux系统下Python调用系统Shell的方法

    举例讲解Linux系统下Python调用系统Shell的方法

    这篇文章主要介绍了举例讲解Linux系统下Python调用系统Shell的方法,包括用Python和shell读取文件某一行的实例,需要的朋友可以参考下
    2015-11-11
  • Pytest如何使用mark的方法

    Pytest如何使用mark的方法

    本文主要介绍了Pytest如何使用mark的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • macbook如何彻底删除python的实现方法

    macbook如何彻底删除python的实现方法

    本文主要介绍了macbook如何彻底删除python的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Python bytes string相互转换过程解析

    Python bytes string相互转换过程解析

    这篇文章主要介绍了Python bytes string相互转换过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Python中isinstance和hasattr的实现示例

    Python中isinstance和hasattr的实现示例

    本文详细介绍了Python中的两个内置函数isinstance和hasattr,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12
  • 入门tensorflow教程之TensorBoard可视化模型训练

    入门tensorflow教程之TensorBoard可视化模型训练

    在本篇文章中,主要介绍 了TensorBoard 的基础知识,并了解如何可视化训练模型中的一些基本信息,希望对大家的TensorBoard可视化模型训练有所帮助
    2021-08-08

最新评论