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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章