深度解析Python的赋值、浅拷贝、深拷贝

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 直接赋值: 其实就是指向对象的引用(别名)。浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。但对于不可变数据类型,不会拷贝,仅仅是指向深拷贝(deepcopy):`copy` 模块的 `deepcopy` 方法,完全拷贝了父对象及其子对象。拷贝 就是把原数据复制一份,在复制的数据上随意改动不会影响到其原数据。也就是这里讲的深拷贝。

简述

  • 直接赋值: 其实就是指向对象的引用(别名)。
  • 浅拷贝(copy): 拷贝父对象,不会拷贝对象的内部的子对象。但对于不可变数据类型,不会拷贝,仅仅是指向
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

拷贝 就是把原数据复制一份,在复制的数据上随意改动不会影响到其原数据。也就是这里讲的深拷贝。

<br/>

直接赋值


In [1]: a = [11, 22, 33]

In [2]: b = a

In [3]: b
Out[3]: [11, 22, 33]

In [4]: id(a), id(b)
Out[4]: (2053851155016, 2053851155016)

In [5]: c = {"name": "hui"}

In [6]: d = c

In [7]: id(c), id(d)
Out[7]: (2053851035112, 2053851035112)

    
In [8]: a.append(44)

In [9]: a
Out[9]: [11, 22, 33, 44]

In [10]: b
Out[10]: [11, 22, 33, 44]

In [11]: c["age"] = 21

In [12]: c
Out[12]: {'name': 'hui', 'age': 21}

In [13]: d
Out[13]: {'name': 'hui', 'age': 21}

可以看到当给一个变量直接赋值时,其实就是将对象引用复制了一份,所以 id() 取得内存地址都一样,它们指向的都是同一个对象。画张图就好理解。

直接赋值

<br/>

浅拷贝

利用内置模块 copy 实现浅拷贝

In [12]: a = [1, 2]

In [13]: b = [3, 4]

In [14]: c = [a, b]

In [15]: d = c

In [16]: id(c),          id(d)
Out[16]: (1409068540040, 1409068540040)

In [17]: import copy

In [18]: e = copy.copy(c)

In [19]: id(c),          id(d),         id(e)
Out[19]: (1409068540040, 1409068540040, 1409070776520) 
# 浅拷贝copy的对象id()不一样

In [20]: id(c[0]), id(c[1])
Out[20]: (1409071493512, 1409071679112)

In [21]: id(e[0]), id(e[1])
Out[21]: (1409071493512, 1409071679112)
# 其子对象则一样

In [22]: a.append(5)

In [23]: b.append(6)

In [24]: c
Out[24]: [[1, 2, 5], [3, 4, 6]]

In [25]: d
Out[25]: [[1, 2, 5], [3, 4, 6]]

In [26]: e
Out[26]: [[1, 2, 5], [3, 4, 6]]
# 内容一致
    
In [28]: c.append(7)

In [29]: c
Out[29]: [[1, 2, 5], [3, 4, 6], 7]

In [30]: e
Out[30]: [[1, 2, 5], [3, 4, 6]]

可以看出直接赋值 cd 是同一对象,而浅拷贝 copyce 是一个分别独立的对象,但他们的子对象 a , b 还是 指向统一对象即引用

因此当 c.append(7) 后,只有 c 对象改变了,而浅拷贝的 e 还是没有变化。

a.append(5), b.append(6) 后,c, d, e 对象依然内容一致。

直接赋值与浅拷贝对比

<br/>

深拷贝

通过 copy.deepcopy() 来实现深拷贝

In [33]: a = [1, 2]

In [34]: b = [3, 4]

In [35]: c = [a, b]

In [36]: d = copy.deepcopy(c)

In [37]: id(c), id(d)
Out[37]: (1409071919752, 1409071607112)

In [38]: id(c[0]), id(c[1])
Out[38]: (1409071948680, 1409071766216)

In [39]: id(d[0]), id(d[1])
Out[39]: (1409071976328, 1409071919880)
# 完全拷贝了,其子对象的id()都不一样

In [40]: c.append(5)

In [41]: c
Out[41]: [[1, 2], [3, 4], 5]

In [42]: d
Out[42]: [[1, 2], [3, 4]]

In [43]: a.append(3)

In [44]: b.append(5)

In [45]: c
Out[45]: [[1, 2, 3], [3, 4, 5], 5]

In [46]: d
Out[46]: [[1, 2], [3, 4]]
# 因此任c怎么修改,都影响不到d

In [47]: d[0].append(5)

In [48]: d[1].append(6)

In [49]: d
Out[49]: [[1, 2, 5], [3, 4, 6]]

In [50]: d.append(7)

In [51]: d
Out[51]: [[1, 2, 5], [3, 4, 6], 7]

In [52]: c
Out[52]: [[1, 2, 3], [3, 4, 5], 5]
# d怎么修改也影响不到c

深度拷贝, 完全拷贝了父对象及其子对象,两者是完全独立的。因此 c,d 做任何操作都互不影响。

深拷贝

<br/>

三者对比

  • d = c 赋值引用,cd 都指向同一个对象
  • e = c.copy() 浅拷贝,ce 是一个 独立的对象,但他们的 子对象还是指向统一对象即引用
  • f = copy.deepcopy(c) 深度拷贝,c f 完全拷贝了父对象及其子对象,两者是完全独立的

深浅拷贝理解图1

<br/>

拷贝对可变类型和不可变类型的区别

  • copy.copy() 对于可变类型,会进行浅拷贝。
  • copy.copy() 对于不可变类型,不会拷贝,仅仅是指向。
  • copy.deepcopy() 深拷贝对可变、不可变类型都一样递归拷贝所有,对象完全独立

<br/>

所谓的不可变指的是所指向的内存中的内容不可变。

同一份内存地址,其内容发生了改变,但地址依旧不变。说明是可变数据类型例如 list, set, dict

数据类型 是否可变
整型 不可变
字符串 str 不可变
元组 tuple 不可变
列表 list 可变
集合 set 可变
字典 dict 可变

<br/>

不可变数据类型测试

In [15]: a = 5
​
In [16]: id(a)
Out[16]: 140712197127360
​
In [17]: a = 6
​
In [18]: id(a)
Out[18]: 140712197127392
    

首先 a=5,地址为 140712197127360,当我让 a=6 把里面值改变时,它并不是说把 a地址 里面值从 5 改成 6, 而是说又开辟了一块新的内存地址140712197127392 里面存储 6,然后让 a 指向 6 的那份内存地址。所以称 a 这种数据类型为不可变数据类型

可变数据类型更改图示

可变数据类型测试

In [31]: li = [1, 2, 3]
​
In [32]: id(li)
Out[32]: 2365035049224
​
In [33]: li[0] = 5
​
In [34]: li
Out[34]: [5, 2, 3]
​
In [35]: id(li)
Out[35]: 2365035049224

li 首先存储了 [1, 2, 3], 后来变成了 [5, 2, 3],但 li 的地址依旧不变,因此称 li 这种数据类型为 可变数据类型

<br/>

浅拷贝测试

In [54]: # 可变类型list

In [55]: a = [1, 2, 3]

In [56]: b = copy.copy(a)

In [57]: id(a)
Out[57]: 1409069563528

In [58]: id(b)
Out[58]: 1409071719752

In [59]: a.append(4)

In [60]: a
Out[60]: [1, 2, 3, 4]

In [61]: b
Out[61]: [1, 2, 3]

In [63]: # 不可变类型 tuple

In [64]: c = (1, 2, 3)

In [65]: d = copy.copy(c)

In [66]: id(c)
Out[66]: 1409070834456

In [67]: id(d)
Out[67]: 1409070834456

当浅拷贝 copy() 不可变类型元组时 tuplec, d的内存地址都一样,说明仅是指向。

<br/>

深拷贝测试

In [71]: a = ([1, 2], [3, 4])

In [72]: b = copy.copy(a)

In [73]: c = copy.deepcopy(a)

In [74]: id(a)
Out[74]: 1409068519944

In [75]: id(b)
Out[75]: 1409068519944
# 浅拷贝不可变类型id()一致

In [76]: id(c)
Out[76]: 1409071533448
# 深拷贝不可变类型id()不一致

In [77]: b[0].append(3)

In [78]: b[1].append(5)

In [79]: a
Out[79]: ([1, 2, 3], [3, 4, 5])

In [80]: b
Out[80]: ([1, 2, 3], [3, 4, 5])

In [81]: c
Out[81]: ([1, 2], [3, 4])
# 浅拷贝的子对象引用一致

In [82]: c[0].append(3)

In [83]: c
Out[83]: ([1, 2, 3], [3, 4])

In [84]: b
Out[84]: ([1, 2, 3], [3, 4, 5])

In [85]: a
Out[85]: ([1, 2, 3], [3, 4, 5])
# 深拷贝则是完全拷贝,互不影响

copy.deepcopy() 深拷贝对可变、不可变类型都一样递归拷贝所有,对象完全独立。

深浅拷贝理解图2

<br/>

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。
相关文章
|
7天前
|
监控 算法 安全
内网桌面监控软件深度解析:基于 Python 实现的 K-Means 算法研究
内网桌面监控软件通过实时监测员工操作,保障企业信息安全并提升效率。本文深入探讨K-Means聚类算法在该软件中的应用,解析其原理与实现。K-Means通过迭代更新簇中心,将数据划分为K个簇类,适用于行为分析、异常检测、资源优化及安全威胁识别等场景。文中提供了Python代码示例,展示如何实现K-Means算法,并模拟内网监控数据进行聚类分析。
28 10
|
25天前
|
存储 算法 安全
控制局域网上网软件之 Python 字典树算法解析
控制局域网上网软件在现代网络管理中至关重要,用于控制设备的上网行为和访问权限。本文聚焦于字典树(Trie Tree)算法的应用,详细阐述其原理、优势及实现。通过字典树,软件能高效进行关键词匹配和过滤,提升系统性能。文中还提供了Python代码示例,展示了字典树在网址过滤和关键词屏蔽中的具体应用,为局域网的安全和管理提供有力支持。
50 17
|
28天前
|
运维 Shell 数据库
Python执行Shell命令并获取结果:深入解析与实战
通过以上内容,开发者可以在实际项目中灵活应用Python执行Shell命令,实现各种自动化任务,提高开发和运维效率。
56 20
|
1月前
|
数据采集 供应链 API
Python爬虫与1688图片搜索API接口:深度解析与显著收益
在电子商务领域,数据是驱动业务决策的核心。阿里巴巴旗下的1688平台作为全球领先的B2B市场,提供了丰富的API接口,特别是图片搜索API(`item_search_img`),允许开发者通过上传图片搜索相似商品。本文介绍如何结合Python爬虫技术高效利用该接口,提升搜索效率和用户体验,助力企业实现自动化商品搜索、库存管理优化、竞品监控与定价策略调整等,显著提高运营效率和市场竞争力。
83 3
|
1月前
|
存储 Python
[oeasy]python056_python中下划线是什么意思_underscore_理解_声明与赋值_改名字
在Python中,下划线有多种用途。单下划线(_)常用于避免命名冲突,如将`max`改为`max_`以保留内置函数功能。双下划线(__var__)通常用于特殊方法和属性,如`__name__`、`__doc__`等。此外,单下划线(_)在交互式解释器中表示上一次表达式的值,但不建议作为普通变量名使用。总结:下划线用于避免命名冲突及特殊标识。
35 1
|
2月前
|
Python
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
52 14
|
2月前
|
数据挖掘 vr&ar C++
让UE自动运行Python脚本:实现与实例解析
本文介绍如何配置Unreal Engine(UE)以自动运行Python脚本,提高开发效率。通过安装Python、配置UE环境及使用第三方插件,实现Python与UE的集成。结合蓝图和C++示例,展示自动化任务处理、关卡生成及数据分析等应用场景。
178 5
|
2月前
|
数据采集 JSON API
如何利用Python爬虫淘宝商品详情高级版(item_get_pro)API接口及返回值解析说明
本文介绍了如何利用Python爬虫技术调用淘宝商品详情高级版API接口(item_get_pro),获取商品的详细信息,包括标题、价格、销量等。文章涵盖了环境准备、API权限申请、请求构建和返回值解析等内容,强调了数据获取的合规性和安全性。
|
Python
python 赋值语句
python 赋值语句
1968 0
|
23天前
|
存储 缓存 Java
Python高性能编程:五种核心优化技术的原理与Python代码
Python在高性能应用场景中常因执行速度不及C、C++等编译型语言而受质疑,但通过合理利用标准库的优化特性,如`__slots__`机制、列表推导式、`@lru_cache`装饰器和生成器等,可以显著提升代码效率。本文详细介绍了这些实用的性能优化技术,帮助开发者在不牺牲代码质量的前提下提高程序性能。实验数据表明,这些优化方法能在内存使用和计算效率方面带来显著改进,适用于大规模数据处理、递归计算等场景。
58 5
Python高性能编程:五种核心优化技术的原理与Python代码

热门文章

最新文章