Python:赋值,浅拷贝(copy)和深拷贝(deepcopy)

2023-11-12

基础知识请查看之前博客:Python 对象:可变对象与不可变对象

赋值、浅拷贝和深拷贝的关键问题:

修改一个变量,会不会导致另外拷贝出来的对象的改变。

不可变对象:

import copy

a1 = 0
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)

print(id(a1))
print(id(a2))
print(id(a3))
print(id(a4))
print('*' * 30)

b1 = 3.5
b2 = b1
b3 = copy.copy(b1)
b4 = copy.deepcopy(b1)

print(id(b1))
print(id(b2))
print(id(b3))
print(id(b4))
print('*' * 30)


c1 = 'abc'
c2 = c1
c3 = copy.copy(c1)
c4 = copy.deepcopy(c1)

print(id(c1))
print(id(c2))
print(id(c3))
print(id(c4))
print('*' * 30)

d1 = (1, 2, 3)
d2 = d1
d3 = copy.copy(d1)
d4 = copy.deepcopy(d1)

print(id(d1))
print(id(d2))
print(id(d3))
print(id(d4))
print('*' * 30)

结果如下:
在这里插入图片描述
我们可以看到:对于可变对象来说,复制赋值、浅拷贝和深拷贝都是一样的,因为你修改的时候,由于是不可变对象,所以你修改的时候都会创建一个新的对象,然后要修改的变量重新指向新创建的对象。
例如:

import copy

a1 = 0
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)

print(id(a1))
print(id(a2))
print(id(a3))
print(id(a4))
print('*' * 30)

a1 = 10
a2 = 20
print(id(a1))
print(id(a2))
print(id(a3))
print(id(a4))
print('*' * 30)

结果如下:
在这里插入图片描述
我们修改 a1 和 a2 都是指向了新的对象,而 a3 和 a4 都还是原来的对象。

可变对象:

import copy

a1 = [[1, 2, 3], 'abc', 4]
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)

print('Anchor')
print(a1)
print(id(a1))
print('=')
print(a2)
print(id(a2))
print('Copy')
print(a3)
print(id(a3))
print('Deepcopy')
print(a4)
print(id(a4))
print('*' * 30)

结果如下:
在这里插入图片描述
我们发现:

  1. 复制赋值操作一样,并没有创建新的对象,而是创建了一个新的变量指向这个对象,也就是这个对象的一个新的别名。
  2. 浅拷贝和深拷贝都新建了一个新的对象,然后各自的变量名指向新的对象。

进一步,我们去看变量中每个元素的 id:

import copy

a1 = [[1, 2, 3], 'abc', 4]
a2 = a1
a3 = copy.copy(a1)
a4 = copy.deepcopy(a1)

print('Item in a1')
for i in a1:
    print(id(i))
print('*' * 30)
print('Item in a2')
for i in a2:
    print(id(i))
print('*' * 30)
print('Item in a3')
for i in a3:
    print(id(i))
print('*' * 30)
print('Item in a4')
for i in a4:
    print(id(i))

在这里插入图片描述
我们发现:

  1. 复制赋值操作没什么意外,毕竟都是指向同一对象,进而里面每个元素的 id 也是一样的。
  2. 浅拷贝里面的元素不管是可变对象(例如第一个元素 [1, 2, 3])还是不可变对象(‘abc’,4)的 id 都是一样的,也就是说浅拷贝只是新建了一个对象,然后对象内部的元素和原对象的元素都是同样的。相互之间的修改可能会有影响。
  3. 深拷贝里面,对于可变对象(例如第一个元素 [1, 2, 3])id 是不同的,这个时候修改就完全独立了。这时候又发现不可变对象的 id 是一样的啊。这个啊,这个是为了节约内存,毕竟对不可变对象的修改都需要新建对象,然后重新指向啊。而可变对象的修改是在原有对象基础上的,所以深拷贝考虑这两者的差异,不可变对象 id 变了,可变对象的 id 不变。

总结:

  1. 复制赋值:没有新建任何对象,就是原对象的一个别名,对象 id 和对象元素的 id 都是一样的。
  2. 浅拷贝:Python 会分配一块新的内存用于创建新的拷贝对象,但拷贝对象中的元素依旧是原对象(被拷贝对象)中元素,即拷贝对象与原对象的 id 不同,但两者中的元素具有相同的 id。
  3. 深拷贝:Python会分配一块新的内存用于创建新的拷贝对象,拷贝对象中的元素是通过递归的方式将原对象中的元素一一复制过来的(可变元素除外,可变元素会创建一个新的对象,然后用原来对象的相应元素赋值之后指向新对象的元素),即对象与对象中的元素都是不同的 id,两者完全独立分离。

实例:

有了上面的理解,我们可以很容易的解释下面的行为:

Case1:

import copy

l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200411104234853.png
显然复制对象之中前两个 id 一致,最后一个 id 不同。浅拷贝出来的对象里面的元素指向也相同。

Case 2:

import copy

l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))


l1[0].append(3)

print(l1)
print(l2)
print(l3)

print('')
print("After action")
print('')

print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))

在这里插入图片描述
显然对原对象中的可变对象元素修改会影响浅拷贝的对象,因为可变对象元素指向同一对象,并且由于是可变对象,所以修改的时候在原有对象上面修改 (l1 的第一个元素的 id 没有变),因此会有影响。
浅拷贝对象的可变对象元素的修改,相互之间影响。

Case 3:

import copy

l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))


l3[0].append(4)

print(l1)
print(l2)
print(l3)

print('')
print("After action")
print('')

print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))

在这里插入图片描述
修改复制出来的对象中的可变对象元素行为也是一样的:会相互影响!

Case 4:

import copy

l1 = [[1, 2], (3, 4)]
l2 = l1
l3 = copy.copy(l1)
print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))


l1[1] += (5, 6)

print(l1)
print(l2)
print(l3)

print('')
print("After action")
print('')

print(id(l1))
print(id(l2))
print(id(l3))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l2')
for i in l2:
    print(id(i))
print('Item in l3')
for i in l3:
    print(id(i))

在这里插入图片描述
修改原对象中不可变对象元素,由于不可变对象不能直接修改,所以需要新建一个元素对象,然后重新指向。我们可以看到 l2 和 l1 一样(指向同一对象,同一对象的别名),都响应了变化,并且第二个元素的 id 也发现了变化。但是 l3 没有任何变化,这是因为它还是指向修改之前的对象。
浅拷贝对象的不可变对象元素的修改,相互不影响。

Case 5:

import copy

l1 = [[1, 2], (3, 4)]
l4 = copy.deepcopy(l1)
print(id(l1))
print(id(l4))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l4')
for i in l4:
    print(id(i))

在这里插入图片描述
显然,为了避免浅拷贝中可变对象相互影响的问题,深拷贝为要复制对象的可变对象元素新建了一个对象,然后赋值,最后深拷贝对象的相应元素指向它。则也就是 l1 和 l4 第一个元素 id 不同的原因,对于可变对象来说,为了节约内存,还是指向同一对象,毕竟修改的时候都会新建对象。

Case 6:

import copy

l1 = [[1, 2], (3, 4)]
l4 = copy.deepcopy(l1)
print(id(l1))
print(id(l4))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l4')
for i in l4:
    print(id(i))

l1[0] += [3]
l1[1] += (5, 6)

print(l1)
print(l4)

print('')
print("After action")
print('')

print(id(l1))
print(id(l4))
print('*'*30)
print('Item in l1')
for i in l1:
    print(id(i))
print('Item in l4')
for i in l4:
    print(id(i))

在这里插入图片描述
相应的,可变对象元素的修改还是在原来对象上的,所以修改前后 l1 的第一个元素 id 不变,但是由于深拷贝的时候为可变对象元素时新建了一个对象的,所以不会影响。不可变对象元素的修改会新建一个对象,所以修改前后 l1 的第二个元素 id 发生了变化,即使 l4 的第二个元素和原来的 id 一样,也是不同的一个值。
深拷贝对象的可变对象元素的修改,相互之间也不影响了。

其他:

浅拷贝除了 copy.copy() 之外还有工厂模式(例如列表的 list(l1))和切片操作(l3 = l1[::])产生。

总结:

理解关键:

  1. 可变对象和不可变对象的修改行为差异:可变对象修改在原有对象上,不可变对象的修改是新建一个对象,赋予新值,然后重新指向。
  2. 浅拷贝只是新建了外层对象,内部元素还是和原对象的内部元素指向一样,因此浅拷贝的可变对象元素的修改会相互影响。
  3. 深拷贝不仅新建了外层对象,还为内部元素中的可变对象元素新建了对象,因此深拷贝的可变对象元素的修改相互不会影响。(不可变对象由于修改一定会新建对象,因此深拷贝中的内部元素的不可变对象也是直接拷贝过来的(id 相同))。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python:赋值,浅拷贝(copy)和深拷贝(deepcopy) 的相关文章

随机推荐

  • python-数据分析(12-时间序列)

    Pandas 12 Pandas之时间序列 12 1 时间序列 时间序列前言 时间序列数据在很多领域都是重要的结构化数据形式 比如 金融 神经科学 生态学 物理学 在多个时间点观测的数据形成了时间序列 时间序列可以是固定频率的 也可以是不规
  • 计算机专业论文 方向,计算机专业本科生方向论文题目 计算机专业本科生论文题目怎样取...

    100道 计算机专业本科生方向论文题目供您参考 希望能解决毕业生们的计算机专业本科生论文题目怎样取相关问题 选好题目那就开始写计算机专业本科生论文吧 一 比较好写的计算机专业本科生论文题目 1 对本科生计算机课程教学改革的探讨 2 浅谈对本
  • 《UNIX环境高级编程》笔记 第十三章-守护进程

    1 概念 守护进程 daemon 是生存期长的一种进程 它们常常在系统引导装入时启动 仅在系统关闭时才终止 因为它们没有控制终端 所以说它们是在后台运行的 Linux的大多数服务就是用守护进程实现的 这些守护进程名通常以d结尾 如inetd
  • ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; con

    作业背景 node mysql 附 想必各位点进来都是遇到了这个错误 我们都知道报错提示的是出问题的范围 而在范围内有着许多不确定的因素 抽象些来说就是导致脱发的原因不一定是熬夜 还有可能是压力大 焦虑 疾病等原因 问题描述 ER NOT
  • Centos离线手动安装gcc

    正常联网情况下 在Centos系统中 我们可以使用yum命令很方便的从网络自动下载和安装gcc编译器 但是 由于各种原因 在实际使用中 Centos系统系统不允许介入互联网 所以只能自己手动下载 上传至服务器 再自己安装编译器 网上可以找到
  • 云计算 第3章虚拟化技术(2)

    物理网络与虚拟网络 CPU虚拟化 CPU及指令集 CPU的操作由它所执行的指令确定 这些指令成为机器指令Machine Instruction 目前 X86服务器在企业中的部署与应用越来越广泛 X86是一个Intel通用计算机系列的编号 也
  • Sqoop使用汇总

    命令 查看数据库 sqoop list databases connect jdbc mysql 127 0 0 1 3306 username root password root 查看表 sqoop list tables connec
  • 巨详细,大电流线性电源(LDO)原理,看完你就明白了

    原文来自公众号 工程师看海 上一篇文章介绍了PMOS结构线性电源的基本工作原理 今天结合仿真介绍大电流LDO使用的NMOS 架构基本工作原理 以及其他一些重要的LDO参数 包括PSRR Dropout Voltage等 添加微信 chunh
  • 对于模式的思考

    一是确定那些知识是需要掌握的 第二则是如何掌握 Pattern无疑是需要学习的 但事实是它很容易被遗忘 却很难在实际工作中熟练地运用 方法就是将解决问题的模式与实际中某个重要的应用match起来 1 Structural Pattern D
  • 数据整理——大数据治理的关键技术

    摘要 数据是政府 企业和机构的重要资源 数据治理关注数据资源有效利用的众多方面 如数据资产确权 数据管理 数据开放共享 数据隐私保护等 从数据管理的角度 探讨了数据治理中的一项关键技术 数据整理 介绍了以数据拥有者和直接使用者 行业用户 为
  • 如何引导机器?如何面临人机结合?《​人工智能与人类未来》

    微信公众号 乐生活与爱 公众号曾转载过蔡恒进教授的奇文 意识如何演化 机器何时有自我意识 附着与隧通 心智的工作模式 值得反复也读 我上周听了由北京大学博古睿研究中心 中国人民大学哲学院 中国人民大学哲学与认知科学交叉平台 服务器艺术联合主
  • “孔乙己的长衫”:学历究竟成为敲门砖还是枷锁

    在社会上 学历是一个很重要的指标 学历不高的人 在工作中很难晋升到管理层 学历高的人 则可以获得更多的机会 但事实上 这只是一个表面现象 更多的是学历给人带来的附加值 我虽然知道这世界上有一个孔乙己 但我却并不知道他生活在哪个城市 如果可以
  • css文本超出2行显示...

    可以使用CSS的text overflow和ellipsis属性来实现文本超过2行时显示省略号 设置文本溢出隐藏 使用CSS的overflow属性来将文本溢出部分隐藏 同时使用white space属性来设置文本换行方式为normal或者n
  • 关于struts漏洞

    文章出处 阿里云社区 漏洞扫描 gt 技术运维问题 gt 技术分享 gt Struts2代码执行漏洞 Apache Struts s2 005 远程代码执行漏洞 CVE 2010 1870 受影响版本 Struts 2 0 0 Struts
  • Kconfig:'endmenu' in different file than 'menu'

    问题描述 heat ubuntu AM5728 update u boot 2017 01 g70d59ba v2 0 make ARCH arm menuconfig scripts kconfig mconf Kconfig drive
  • CGAL 点云随机下采样

    目录 一 概述 1 主要函数 二 代码实现 三 结果展示 一 概述 随机删除用户指定的输入点的一部分 1 主要函数 头文件 include
  • 解决PowerShell不显示conda虚拟环境的问题

    目录 1 指令正常执行和结果 2 指令执行异常以及解决办法 问题1 CommandNotFoundError No command conda conda 问题2 conda init powershell执行完毕后 重启PowerShel
  • 工作中经常使用shell脚本

    在工作中我们常用shell脚本处理一些问题 今天在来一些这里整理了一些工作中常用的简单shell脚本 1 更新脚本 bin bash apt get update DEBIAN FRONTEND noninteractive apt get
  • 【C语言】小游戏-扫雷(清屏+递归展开+标记)

    大家好 我是深鱼 目录 一 游戏介绍 二 文件分装 三 代码实现步骤 1 制作简易游戏菜单 2 初始化棋盘 11 11 3 打印棋盘 9 9 4 布置雷 5 计算 x y 周围8个坐标的和 6 排查雷 lt 1 gt 清屏后打印棋盘 lt
  • Python:赋值,浅拷贝(copy)和深拷贝(deepcopy)

    基础知识请查看之前博客 Python 对象 可变对象与不可变对象 赋值 浅拷贝和深拷贝的关键问题 修改一个变量 会不会导致另外拷贝出来的对象的改变 不可变对象 import copy a1 0 a2 a1 a3 copy copy a1 a