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

相关文章

  • Django用户认证系统 组与权限解析

    Django用户认证系统 组与权限解析

    这篇文章主要介绍了Django用户认证系统 组与权限解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • Python islower()函数的使用详解

    Python islower()函数的使用详解

    这篇文章主要介绍了Python islower()函数使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 学习Python爬虫前必掌握知识点

    学习Python爬虫前必掌握知识点

    这篇文章主要介绍了学习Python爬虫前,我们需要了解涉及爬虫的知识点,学习爬虫的知识点比较多,我们一起学习爬虫吧
    2021-04-04
  • Django中Aggregation聚合的基本使用方法

    Django中Aggregation聚合的基本使用方法

    这篇文章主要给大家介绍了关于Django中Aggregation聚合的基本使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者使用Django具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-07-07
  • Django中传递参数到URLconf的视图函数中的方法

    Django中传递参数到URLconf的视图函数中的方法

    这篇文章主要介绍了Django中传递参数到URLconf的视图函数中的方法,Django是最具人气的Python开发框架,需要的朋友可以参考下
    2015-07-07
  • PyCharm使用matplotlib报MatplotlibDeprecationWarning问题解决办法

    PyCharm使用matplotlib报MatplotlibDeprecationWarning问题解决办法

    这篇文章主要给大家介绍了关于PyCharm使用matplotlib报MatplotlibDeprecationWarning问题解决的相关资料,主要是 matplotlib版本过高导致的,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • linux环境下Django的安装配置详解

    linux环境下Django的安装配置详解

    这篇文章主要介绍了linux环境下Django的安装配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • 实例讲解python读取各种文件的方法

    实例讲解python读取各种文件的方法

    这篇文章主要为大家详细介绍了python读取各种文件的方法,,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • 在前女友婚礼上用python把婚礼现场的WIFI名称改成了

    在前女友婚礼上用python把婚礼现场的WIFI名称改成了

    大家好,我是Lex 喜欢欺负超人那个Lex 擅长领域:python开发,网络安全渗透,Windows域控Exchange架构 今日重点:python暴力拿下WiFi密码;python拿下路由器管理页面 代码干货满满,建议收藏+实操!有问题及需要,请留言哦
    2021-08-08
  • Python解析JSON对象的全过程记录

    Python解析JSON对象的全过程记录

    这篇文章主要给大家介绍了关于Python解析JSON对象的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03

最新评论