zoukankan      html  css  js  c++  java
  • Python基础-17对象引用和拷贝

    17.对象引用和拷贝

        我们先来看看以下向个概念

    • 变量:是系统变量名表中的元素,通常是由程序员进行定义声明
    • 对象:是计算机分配的一块内存,需要足够的空间去表示它的值
    • 引用:是自动形成的从变量到对象的指针
    • 可变对象:允许对自身内容进行修改。如list、dict、set、自定义类型等。
    • 不可变对象:不允许对自身内容进行修改。如果对一个不可变对象进行赋值,实际上是生成一个新的对象,再让变量指向这个对象。如int、float、bool、str、tuple

    如果有了解Java的堆栈知识(堆存储真实的数据,而栈则是存储相应引用地址),则这里所指的对象可以理解为堆,而引用则代表栈。

    17.1 对象引用

        对象的赋值实际上就是对象引用,创建一个对象并将其赋值给一个变量时,该变量实际是指向了该对象的引用,可使用内置函数id()查看返回值。变量名与对象之间的示意图如下所示:

    170101对象与变量名关系.png

    示例如下所示:

    >>> tempA=[1,3,5]
    >>> tempB=tempA    # tempB对tempA的引用
    >>> tempB
    [1, 3, 5]
    >>> tempB[0]=-100  # 修改tempB的元素,tempA相应的元素也同步进行了更改
    >>> tempA
    [-100, 3, 5]
    >>> tempB
    [-100, 3, 5]
    >>> id(tempA),id(tempB)
    (2614814009544, 2614814009544)
    >>> tempB is tempA
    True
    

        在上面的例子中,本意是想修改tempB中第一个元素,而连带temA也被一起修改了。因为tempA和tempB引用的是同一个对象,修改其中任意一个变量都会影响到另一个。为了避免这种情况,必须创建对象的副本而不是创建新引用。对于像列表和字典这种容器类对象,可以使用两种拷贝操作:浅拷贝深拷贝

    17.2 对象的拷贝

    17.2.1 浅拷贝

        浅拷贝将创建一个新对象,其内容是原对象中元素的引用。可以使用模块copy中的copy()函数,另外也可使用切片操作、对象的copy方法。其特点如下所示:

    • 两个变量的内存地址不同
    • 变量之间存在共享值的情况
    • 对其中一个变量进行更改后,另外的变量也会随之改变

    如果使用等号赋值时,连对象都不会重新创建。只有重新创建对象并为其赋值,才会发生浅拷贝

        示例代码如下所示:

    >>> a=[1,2,[3,4]]
    >>> b=list(a)          # 创建a的一个浅复制
    >>> b is a
    False
    >>> b.append(100)      # 给b追加一个元素
    >>> b
    [1, 2, [3, 4], 100]    # 修改b中的一个元素
    >>> a
    [1, 2, [3, 4]]
    >>> b[2][0]=-98
    >>> b
    [1, 2, [-98, 4], 100]
    >>> a                  # a中与b共有的元素值也会发生改变
    [1, 2, [-98, 4]]
    >>> id(a),id(b)
    (2614813897288, 2614813796232)
    
    >>> aa=[1,2,[3,4]]
    >>> bb=aa              # 直接赋值并没有发生浅拷贝
    >>> id(aa),id(bb)
    (1960262980168, 1960262980168)
    >>> aa = list(bb)
    >>> id(aa),id(bb)
    (1960263019208, 1960262980168) # 发生了浅拷贝,因此两者的id也不一样
    >>> id(aa[0]),id(aa[1]),id(aa[2])
    (140715523797264, 140715523797296, 1960263020232)
    >>> id(bb[0]),id(bb[1]),id(bb[2])
    (140715523797264, 140715523797296, 1960263020232) # 虽然发生了浅拷贝,但内部元素却都指向相同的对象
    
    

        在上述示例中,a和b是单独的列表对象,但它们包含的元素是共享的。因此修改b的一个元素也会修改a中的对应元素。而在aa和bb中,在发生浅拷贝后,aa和bb两个对象的地址不一样,而其内部元素却指向了相同的对象。

    17.2.2 深拷贝

        深拷贝将创建一个新对象并对其赋值时,原对象中的所有元素都会在新对象中重新创建一次。常用模块copy中的deepcopy()函数,其特点如下所示:

    • 变量间的内存地址不同
    • 变量间有各自的值,且互不影响
    • 对其任意一个变量的值进行修改,不会影响另外一个

        示例代码如下所示:

    >>> import copy
    >>> a=[1,2,[3,4]]
    >>> b=copy.deepcopy(a) # 深拷贝
    >>> b is a
    False
    >>> b.append(100)
    >>> b
    [1, 2, [3, 4], 100]
    >>> a
    [1, 2, [3, 4]]
    >>> b[2][0]=-98
    >>> b
    [1, 2, [-98, 4], 100]
    >>> a            # 在修改b之后,对a没有任何影响
    [1, 2, [3, 4]]
    >>> id(a),id(b)
    (1960263017096, 1960263112136)
    
    >>> aa=[1,2,[3,4]]
    >>> bb=copy.deepcopy(aa) # 深拷贝
    >>> id(aa),id(bb)
    (2540655565384, 2540656480520) # 地址发生改变
    >>> id(aa[0]),id(aa[1]),id(aa[2])
    (140715523797264, 140715523797296, 2540655563912)
    >>> id(bb[0]),id(bb[1]),id(bb[2])
    (140715523797264, 140715523797296, 2540656401224)
    

        在打印内部地址发现,前两个元素地址没有属性改变,是因为在Python数字和字符串属于不可变对象。为提升效率,Python语言中,在内存中只存在一份不可变对象,并将其地址(即引用)赋值给其他变量。

    浅拷贝和深拷贝仅仅是针对可变对象的,对于不可变对象,赋值的操作过程都是直接将引用赋值。

    17.3 小结

        现在假设有一个对象a=[ 1, 2 ,[ 3,4 ] ],有另外一个对象,分别进行=赋值、浅拷贝和深拷贝,其使用小结如下所示:

    • 1.使用=直接赋值,不会发生浅拷贝和深拷贝情况,仅相当于增加一个新标签,并不产生新的对象,示意图如下所示:

    170301等号赋值.png

        针对这种情况,有时候也被比喻为旧瓶装旧酒

    • 2.使用浅拷贝之后,会创建一个新的对象,但内部元素仍然保持一致,示意图如下所示:

    170302浅拷贝.png

        因为元素中1和2为不可变对象,它们互不影响,给人的感觉就相当于复制了一份。这种就是浅拷贝,有时候也被比喻为新瓶装旧酒,虽然产生了新的对象,但里面的内容还是来自同一份。

    • 3.使用深拷贝之后,会创建一个新的对象,原对象中的所有元素会被重新创建一次,示意图如下所示:

    170303深拷贝.png

        对象a和b前两个元素因是不可变对象,所会在进行深拷贝之后,地址不会进行更改。而第三个元素为可变对象,则相当创建了一个副本。所以深拷贝也可以理解为,不仅是对象自身的拷贝,对于对象中每一个子元素,也都进行同样的拷贝。针对这种情况,有时候也被比喻为新瓶装新酒

    • 4.浅拷贝和深拷贝针对的是可变对象

    参考网址:https://segmentfault.com/a/1190000017001073

    本文地址:https://www.cnblogs.com/surpassme/p/13028213.html
    本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:
    MyQRCode.jpg

  • 相关阅读:
    ajax 请求登录超时跳转登录页的示例代码
    [WPF]实现密码框的密码绑定
    Linq系列(5)——表达式树之案例应用
    idea设置内存大小
    idea右下角显示使用内存情况
    idea打开Run Dashboard
    java的byte[]与String相互转换
    java有包名的调用没有包名的类,用反射
    【转】查看电脑显卡型号及显卡性能
    idea关闭sonarLint自动扫描
  • 原文地址:https://www.cnblogs.com/surpassme/p/13028213.html
Copyright © 2011-2022 走看看