当前位置: 首页 > article >正文

Python的浅拷贝与深拷贝

一、浅拷贝

      浅拷贝,指的是重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用。

     浅拷贝有几种方法:

     1、 使用数据类型本身的构造器

list1=[1,2,3]list2 = list(list1)  # 使用了数据类型本身的构造器 listprint(list2)print("list1== list2?",list1==list2)print("list1 is list2?", list1 is list2)运行结果:[1, 2, 3] 
list1==list2 ? True 
list1 is list2 ? False

    在上面程序中,list2 就是 list1 的浅拷贝,同理 set2 是 set1 的浅拷贝。还可以通过切片操作完成浅拷贝。

list1 = [1, 2, 3,4,5]
list2 = list1[:]
print(list2)
print("list1 == list2 ?",list1 == list2)
print("list1 is list2 ?",list1 is list2)运行结果为:[1, 2, 3,4,5] 
list1 == list2 ? True 
list1 is list2 ? False

  2、 使用copy.copy()方法

    Python 还提供了对应的函数 copy.copy() 函数,适用于任何数据类型。其用法如下:

import copy
list1 = [1, 2, 3 , 4, 5]
list2 = copy.copy(list1)
print(list2)
print("list1 == list2 ?",list1 == list2)
print("list1 is list2 ?",list1 is list2)运行结果:
[1, 2, 3, 4, 5] 
list1 == list2 ? True 
list1 is list2 ? False

    对于tuple类型,使用 tuple() 、切片操作符 ':' 或 copy.copy() 不会创建一份浅拷贝,相反它会返回一个指向相同元组的引用:

import copytuple1 = (1, 2, 3, 4, 5)
tuple2 = tuple(tuple1)
tuple3 = copy.copy(tuple1)
print(tuple2)
print("tuple1 == tuple2 ?",tuple1 == tuple2)
print("tuple1 == tuple3 ?",tuple1 == tuple3)
print("tuple1 is tuple2 ?",tuple1 is tuple2)
print("tuple1 is tuple3 ?",tuple1 is tuple3)运行结果:
(1, 2, 3, 4, 5)
tuple1 == tuple2 ? True
tuple1 == tuple3 ? True
tuple1 is tuple2 ? True
tuple1 is tuple3 ? True

   对数据采用浅拷贝的方式时,如果元素可变,浅拷贝通常会出现一些问题,例如:

list1 = [[1, 2], (30, 40)]
list2 = list(list1)list1.append(100)
print("list1:",list1)
print("list2:",list2)list1[0].append(3)
print("list1:",list1)
print("list2:",list2)list1[1] += (50, 60)
print("list1:",list1)
print("list2:",list2)运行结果为:
list1: [[1, 2], (30, 40), 100] 
list2: [[1, 2], (30, 40)] 
list1: [[1, 2, 3], (30, 40), 100] 
list2: [[1, 2, 3], (30, 40)] 
list1: [[1, 2, 3], (30, 40, 50, 60), 100] 
list2: [[1, 2, 3], (30, 40)]

    此程序中,首先初始化了 list1 列表,包含一个列表和一个元组;然后对 list1 执行浅拷贝,赋予 list2。因为浅拷贝里的元素是对原对象元素的引用,因此 list2 中的元素和 list1 指向同一个列表和元组对象。 接着往下看,list1.append(100) 表示对 list1 的列表新增元素 100。这个操作不会对 list2 产生任何影响,因为 list2 和 list1 作为整体是两个不同的对象,并不共享内存地址。操作过后 list2 不变,list1 会发生改变。list1 改变后 ,内存视图可以理解为[引用, 引用,100],而list2 任然为[引用, 引用]。

   再看 list1[0].append(3) 表示对 list1 中的第一个列表新增元素 3。因为 list2 是 list1 的浅拷贝,list2 中的第一个元素和 list1 中的第一个元素,共同指向同一个列表,因此 list2 中的第一个列表也会相对应的新增元素 3。

   最后是 list1[1] += (50, 60),因为元组是不可变的,这里表示对 list1 中的第二个元组拼接,然后重新创建了一个新元组作为 list1 中的第二个元素, 而 list2 中没有引用新元组,因此 list2 并不受影响。list1 视图可以理解为:[修改后的引用,新元组,100],list2内存视图可以理解为:[修改后的引用,引用]。

二、深拷贝

    从上清楚地看到使用浅拷贝可能带来的副作用。如果想避免这种副作用,完整地拷贝一个对象,就需要使用深拷贝。所谓深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。

    Python 中以 copy.deepcopy() 来实现对象的深度拷贝。比如上述例子写成下面的形式,就是深度拷贝。

import copy
list1 = [[1, 2], (30, 40)]
list2 = copy.deepcopy(list1)list1.append(100)
print("list1:",list1)
print("list2:",list2)list1[0].append(3)
print("list1:",list1)
print("list2:",list2)list1[1] += (50, 60)
print("list1:",list1)
print("list2:",list2)运行结果:
list1: [[1, 2], (30, 40), 100] 
list2: [[1, 2], (30, 40)] 
list1: [[1, 2, 3], (30, 40), 100] 
list2: [[1, 2], (30, 40)] 
list1: [[1, 2, 3], (30, 40, 50, 60), 100] 
list2: [[1, 2], (30, 40)]

    可以看到,无论 list1 如何变化,list2 都不变。因为此时的 list1 和 list2 完全独立,没有任何联系。如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环。

import copy
list1 = [1]
list1.append(list1)
print(list1)list2 = copy.deepcopy(list1)
print(list2)运行结果为:
[1, [...]] 
[1, [...]]

    此例子中,列表 x 中有指向自身的引用,因此 x 是一个无限嵌套的列表。但是当深度拷贝 x 到 y 后,程序并没有出现栈溢出的现象。这是因为深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回。上述代码中,list1.append(list1)后,如果拷贝到[1, [...]] 中的list1自身,则深度拷贝回直接返回list1本身,因此效果和list.append(list1)一致。deepcopy的部分代码:

def deepcopy(x, memo=None, _nil=[]):
"""Deep copy operation on arbitrary Python objects.See the module's __doc__ string for more info.
"""if memo is None:memo = {}d = id(x) # 查询被拷贝对象 x 的 idy = memo.get(d, _nil) # 查询字典里是否已经存储了该对象if y is not _nil:return y # 如果字典里已经存储了将要拷贝的对象,则直接返回........

http://www.lryc.cn/news/2403710.html

相关文章:

  • VSCode - VSCode 放大与缩小代码
  • 消息队列处理模式:流式与批处理的艺术
  • 11-Oracle 23ai Vector Embbeding和ONNX
  • Build a Large Language Model (From Scratch) 序章
  • 【HarmonyOS 5】教育开发实践详解以及详细代码案例
  • NoSQL 之Redis哨兵
  • 【nano与Vim】常用命令
  • OpenCV 图像色彩空间转换与抠图
  • Amazing晶焱科技:电子系统产品在多次静电放电测试后的退化案例
  • Go 中的 Map 与字符处理指南
  • 互联网大厂Java求职面试:云原生架构下的微服务网关与可观测性设计
  • C++中const关键字详解:不同情况下的使用方式
  • Java 2D 图形类总结与分类
  • C# 快速检测 PDF 是否加密,并验证正确密码
  • 服务器信任质询
  • 华为云Flexus+DeepSeek征文| 华为云Flexus X实例单机部署Dify-LLM应用开发平台全流程指南
  • Python: 操作 Excel折叠
  • IBM官网新闻爬虫代码示例
  • Java持久层技术对比:Hibernate、MyBatis与JPA的选择与应用
  • Spring Boot实现接口时间戳鉴权
  • 视觉SLAM基础补盲
  • STM32外设问题总结
  • Vue-3-前端框架Vue基础入门之VSCode开发环境配置和Tomcat部署Vue项目
  • 动态IP与静态IP:数字世界的“变脸术”与“身份证”
  • “一代更比一代强”:现代 RAG 架构的演进之路
  • My图床项目
  • SpringBoot3项目架构设计与模块解析
  • C#文件压缩与解压缩全攻略:使用ZipFile与ZipArchive实现高效操作
  • 1、Go语言基础中的基础
  • Go语言基础知识总结(超详细整理)