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

posted @ 2020-06-01 23:16  Surpassme  阅读(624)  评论(0编辑  收藏  举报