首页 > 其他分享 >深拷贝与浅拷贝

深拷贝与浅拷贝

时间:2023-09-07 20:23:44浏览次数:32  
标签:对象 list1 列表 print 拷贝 id

1. 可变对象与不可变对象

  在Python中,对象可以分为可变对象(Mutable Object)和不可变对象(Immutable Object)两种类型。可变对象指的是能够在原地修改的对象,即对象的值可以被改变而不需要创建新的对象。常见的可变对象包括列表(list)和字典(dict)。不可变对象指的是不能够被修改的对象,一旦创建后,它的值就不能再被改变,如果要修改它的值,只能创建一个新的对象。常见的不可变对象包括整数(int)、浮点数(float)、字符串(str0和元组(tuple)。

  下面是一些关于可变对象和不可变对象的特点:

  可变对象:

    可以被原地修改,不会改变对象的地址

    可以通过引用多次指向同一个对象

    可以作为字典的键或集合的元素

  不可变对象:

    不能被原地修改,操作会创建一个新的对象

    值的改变会导致创建一个新的对象,旧对象的地址会改变

    具有hash值,可以作为字典的键或集合的元素

  在python中比较两个对象常常用到= =与is

#可变对象列表
c=["123"]
d=["123"]
print(id(c))#2707103765000
print(id(d))#2707103765512
print(id(c[0]))#1287253524400
print(id(d[0]))#1287253524400

  上面的代码中,c的内存地址与d的内存地址不同,因为列表时可变对象,即使相同的两个列表内存地址也不同,使用id函数可以看作得到内存地址,其实是每个对象的唯一标志符。但是id(c[0])与id(d[0])是相同的,因为他们是字符串,字符串是不可变对象。id(c[0])是获取元素"123"的内存地址。对于字符串是不可变的,也就是"123"只有一份。在python3.1开始为了堆内存进行优化,针对不可变对象,提供了对象池(往后看,后面再提到)。对于上面的代码,其存储示意图如下:

  创建c=["123"]后,如下:

  再创建d=["123"]后,如下:

   如此时,给c列表添加一个元素,比如执行了c.append(1),则如下:

 

 

   假如现在我们要修改列表d的字符串'123'为"12",又该如何变化?由于字符串属于不可变对象,是无法被修改的,尽管我们执行了d[0]="12"确实可以执行,一般也是这么做的。但是实时上发生了下图所示的变化,d中的第一个位置的元素引用的是字符串"12"的地址,c中的"123"没有变。

  对于其他可变对象,使用id()函数得到的结果与列表原理差不多,这里暂时不详细讲述了。

  从python3.1开始:不可变数据拥有了对象池,其是一种内存优化机制。对于-5到256的小整数。也就是说,当创建一个整数对象时,如果该整数的值在范围内,Python会尝试从整数对象池中获取已经存在的对象,而不是重新创建一个对象。这样做的好处是可以节省内存和创建对象的开销。实际上,在某些情况下,范围外的整数(不在[-5, 256]范围内)也可能指向同一个对象。这是因为在Python中,解释器可能会对一些整数对象进行缓存以提高性能,因此对于某些整数值,多个变量可能引用同一个对象。然而,这种行为是不可靠的,并且可能因Python解释器的不同版本、不同实现或不同的运行环境而有所不同。因此,我们不能依赖is操作符来判断两个整数对象是否相等。常使用==判断两个整数是否相同,一般没有使用is这样去做。
  看下面的整数代码:
#不可变对象
a=10
b=10
print(id(a))#140714195317296
print(id(b))#140714195317296

  不可变对象还包括元组,这里不举例子了。

2.引用赋值

  这里还是以列表为例子:

list01=["北京","上海"]
list02=list01 #引用赋值
print("list01",id(list01))#1678284182024
print("list02",id(list02))#1678284182024
# list01[0]="广东"
# list03=list01[:]
# list03[-1]="深圳"
# print("list03",id(list03))#1678284182536
# print(list01)#['广东', '上海']
# print(list02)#['广东', '上海']

  看上面的代码,绿色的部分故意注释了。list01与list02的id值相同,因为是采用的引用赋值,前面的c=["123"],d=["123"]是不同的。可以用下图来更加清晰的说明这个问题:

  再注视掉绿色的部分,运行后,结果如注释部分。我们使用 list03=list01[:]  与  list03[-1]="深圳"后,改变了list03,但是list02与list01并没有收到list03的影响,因为我们使用的是list03=list01[:] 赋值,这是采取切片的方式赋值,发生了浅拷贝,关于浅拷贝请继续往下看。

3.浅拷贝

  浅拷贝是一种创建一个新的对象,该对象引用原始对象的元素的副本的过程。浅拷贝只复制了原始对象中的元素的引用,而不是元素本身。

  这里还是以列表为例,看下面的代码:

#copy()浅拷贝
list1 = [1,[3, 4]]
list2 = list1.copy()
print(list1)#[1,[3, 4]]
print(list2)#[1,[3, 4]]
print(id(list1))#1741602956808
print(id(list2))#1741602956936

list1[0]=0
print(list1)#[0,[3, 4]]
print(list2)#[1,[3, 4]]

list1[1][0]=9
print(list1)#[0,[9, 4]]
print(list2)#[1,[9, 4]]

list1[1]=-1
print(list1)#[0,-1]
print(list2)#[1,[9, 4]]

分析如下:执行

list1 = [1,[3, 4]]
list2 = list1.copy()
之后,所发生改变的示意图如下:

   也就是说,如果列表里有元素是列表,则该位置存储了列表的地址。如图中所示。

  执行了list1[0]=0之后,示意图如下如所示:

   也就是说list1[0]=0,这一句实际上是将整数0在内存中的地址赋值给了list1所指向列表的第一个位置上的变量。而不是将1改为0,因为1是不可变对象,是不可能改变的,赋值页仅仅是改变了地址的指向。所以list2是没有变化的。

  执行 list1[1][0]=9之后,变化的示意图如下:

  也就是将9所在的地址赋值给了列表的第一个位置上的变量。所以输出list2时也跟着变化了。

  最后是执行了 list1[1]=-1这一句。示意图如下:

   可以从上面看出,经过一番操作后,list1与list2再也没有共同的元素了。如果想要list1第二个元素再次指向绿色部分的列表,可以将list2列表第二个位置的值(地址)赋值给它就可以了。

4.深拷贝

  深拷贝较为简单,数据完全独立,操作互不影响。就是比较费内存。

import copy

list_name=["北京",["上海","深圳"]]
data01=copy.deepcopy(list_name)#深拷贝
data01[0]="武汉"#互不影响
data01[1][0]="西安"#互不影响
print(list_name)#["北京",["上海","深圳"]]

   执行list_name=["北京",["上海","深圳"]]  data01=copy.deepcopy(list_name)#深拷贝代码后,其存储变化示意图如下:

 

  通过上图发现,与浅拷贝不同,深拷贝的data01的第二个元素是一个新的列表,不再和浅拷贝一样共用。

  执行data01[0]="武汉"   data01[1][0]="西安"这两句后,发生了如下变化:

   小结:文中大部分是以列表为例子进行讲解说明,对引用赋值,浅拷贝,深拷贝进行了比较详细的图解说明。浅拷贝除了文中提到的copy()外,切片也是浅拷贝,也可以使用copy.copy(要拷贝的对象)来实现浅拷贝,浅拷贝的本质是复制了原始对象中的元素的引用。深拷贝对于元素为列表时,不是复制了原列表元素的引用,而是重新分配了一个新的列表。

  

标签:对象,list1,列表,print,拷贝,id
From: https://www.cnblogs.com/wancy/p/17678327.html

相关文章

  • 手撕代码,实现String类的构造函数、拷贝构造函数、赋值构造函数以及析构函数
    #include<bits/stdc++.h>usingnamespacestd;classString{public:String(constchar*str=NULL){//普通构造函数cout<<"普通构造函数被调用"<<endl;if(str==NULL){data=newchar[1];*dat......
  • 浅拷贝和深拷贝实现
    #include<bits/stdc++.h>usingnamespacestd;classstudent{private:char*name;public:student(){name=newchar(20);cout<<"创建student"<<endl;};~student(){cout<<&qu......
  • 给定文件列表,按目录结构拷贝到新目录中
      #!/bin/bash#mycopyTree.sh文件内容如下functionprint_usage(){echo"Usage:${1}<src_list_file><dest_dir>"}functionmycopy_tree(){#输入源文件列表目录src_list_file=${1}#输入目标目录dest_dir=${2}#遍历源......
  • 深入理解零拷贝技术
    注意事项:除了DirectI/O,与磁盘相关的文件读写操作都有使用到pagecache技术。数据的四次拷贝与四次上下文切换很多应用程序在面临客户端请求时,可以等价为进行如下的系统调用:File.read(file,buf,len);Socket.send(socket,buf,len);例如消息中间件Kafka就是这个应用场......
  • 使用JSON.parseObject深拷贝二-复杂型拷贝
    问:如果json拷贝类型类似Page<T>这种复杂型的呢答:使用JSON的publicstatic<T>TparseObject(Stringtext,TypeReference<T>type,Feature...features)方法;代码如下:Page<EtcPassBillDataResponseVo>voPage=JSON.parseObject(JSON.toJSONString(dtoPage),new......
  • 精简深拷贝ArrayList实例(包括递归和序列化方法)
    作者fbysss关键字:深拷贝,序列化前言:     日前一哥们问我一个有关多层ArrayList拷贝的问题,我帮他写了一个例程,感觉以后用得着,便放上来了。如果要在自身类中加入Clone功能,需要implementsICloneable接口,然后用下面的相应代码重写clone方法即可。源代码:packagecom.sss.t......
  • js深拷贝案例
    <!DOCTYPEhtml><htmlclass="no-js"><head><metacharset="utf-8"/><metahttp-equiv="X-UA-Compatible"content="IE=edge"/><title></title>......
  • java拷贝对象列表List copyProperties
    <!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.0.6</version></dependency>/***@Author:Fcx*@Date:2019/11/2020:45*@Versio......
  • 零拷贝
    小林coding《什么是零拷贝》笔记参考:TheLinuxKernel Linux :Conceptsoverview兰新宇 :  Linux中的mmap映射[一]ALEXXU  徐旭 :WhyisKafkafast? 每个存储器只和相邻的一层存储器设备打交道,并且存储设备为了追求更快的速度,所需的材料成本必然也是更高,也正因......
  • c++ 删除 类的拷贝和赋值函数
      #pragmaonce#include"include/cef_app.h"classHttpSchemeFactory:publicCefSchemeHandlerFactory{public:HttpSchemeFactory()=default;//删除拷贝函数HttpSchemeFactory(constHttpSchemeFactory&)=delete;//删除赋值函数H......