17.對象引用和拷貝 我們先來看看以下向個概念 變數:是系統變數名錶中的元素,通常是由程式員進行定義聲明 對象:是電腦分配的一塊記憶體,需要足夠的空間去表示它的值 引用:是自動形成的從變數到對象的指針 可變對象:允許對自身內容進行修改。如list、dict、set、自定義類型等。 不可變對象:不允許 ...
17.對象引用和拷貝
我們先來看看以下向個概念
- 變數:是系統變數名錶中的元素,通常是由程式員進行定義聲明
- 對象:是電腦分配的一塊記憶體,需要足夠的空間去表示它的值
- 引用:是自動形成的從變數到對象的指針
- 可變對象:允許對自身內容進行修改。如list、dict、set、自定義類型等。
- 不可變對象:不允許對自身內容進行修改。如果對一個不可變對象進行賦值,實際上是生成一個新的對象,再讓變數指向這個對象。如int、float、bool、str、tuple
如果有瞭解Java的堆棧知識(堆存儲真實的數據,而棧則是存儲相應引用地址),則這裡所指的對象可以理解為堆,而引用則代表棧。
17.1 對象引用
對象的賦值實際上就是對象引用,創建一個對象並將其賦值給一個變數時,該變數實際是指向了該對象的引用,可使用內置函數id()查看返回值。變數名與對象之間的示意圖如下所示:
示例如下所示:
>>> 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.使用=直接賦值,不會發生淺拷貝和深拷貝情況,僅相當於增加一個新標簽,並不產生新的對象,示意圖如下所示:
針對這種情況,有時候也被比喻為舊瓶裝舊酒。
- 2.使用淺拷貝之後,會創建一個新的對象,但內部元素仍然保持一致,示意圖如下所示:
因為元素中1和2為不可變對象,它們互不影響,給人的感覺就相當於複製了一份。這種就是淺拷貝,有時候也被比喻為新瓶裝舊酒,雖然產生了新的對象,但裡面的內容還是來自同一份。
- 3.使用深拷貝之後,會創建一個新的對象,原對象中的所有元素會被重新創建一次,示意圖如下所示:
對象a和b前兩個元素因是不可變對象,所會在進行深拷貝之後,地址不會進行更改。而第三個元素為可變對象,則相當創建了一個副本。所以深拷貝也可以理解為,不僅是對象自身的拷貝,對於對象中每一個子元素,也都進行同樣的拷貝。針對這種情況,有時候也被比喻為新瓶裝新酒
- 4.淺拷貝和深拷貝針對的是可變對象
參考網址:https://segmentfault.com/a/1190000017001073
本文地址:https://www.cnblogs.com/surpassme/p/13028213.html
本文同步在微信訂閱號上發佈,如各位小伙伴們喜歡我的文章,也可以關註我的微信訂閱號:woaitest,或掃描下麵的二維碼添加關註: