首頁  >  文章  >  後端開發  >  詳解Python淺拷貝、深拷貝及引用機制

詳解Python淺拷貝、深拷貝及引用機制

高洛峰
高洛峰原創
2017-03-23 17:43:071440瀏覽

這禮拜碰到一些問題,然後意識到基礎知識一段時間沒鞏固的話,還是有遺忘的部分,還是需要溫習,這裡做份筆記,記錄一下

##前續
先簡單描述下碰到的題目,要求是寫出2個print的結果


详解Python浅拷贝、深拷贝及引用机制
#可以看到,a指向了一個列表list對象,在Python中,這樣的賦值語句,其實內部意義是指a指向這個list所在記憶體位址,可以看作類似指標的概念。

而b,注意,他是把a對象包進一個list,並且乘以5,所以b的樣子應該是一個大list,裡面元素都是a

而當a對象進行了append操作後,其實,隱含的意思是,內存中的這個list進行了修改,所有對此對象進行引用的對象,都會發生改變

我將a的id打印出來,並且,同時印出b這個物件中所包含的元素a的id,這樣可以看到,在b這個list中,每個元素的id,和a是一樣的


详解Python浅拷贝、深拷贝及引用机制
#我們可以看到,a對象的id(記憶體位址)為10892296,雖然b把a包進了新的list,但是,這個元素引用的,還是相同位址的對象,可以用下圖來解釋##在


详解Python浅拷贝、深拷贝及引用机制 #之後,我們對a進行了append操作,由於list是可變對象,所以,他的記憶體位址並沒有改變,但是,對於記憶體中這個位址的引用的所有對象,都會被一同改變可以從上面測試圖分割線下半部分看出來.

由此,引出了對Python引用機制和淺複製及深複製的複習


Python的引用機制



引用機制案例1
由上面的例子,我們可以看到,python的引用傳遞,最終結果是讓2個物件都引用記憶體中同一塊區域的內容

所以我們來看一下下面的範例

B透過A,同樣引用了id為17446024的地址的內容,2者的id(記憶體位址)都是一毛一樣的

所以,透過A的操作 A[0]=3  或是  A[3].append(6)  ,都會對這塊記憶體中的內容進行修改(因為list是可變對象,所以記憶體位址並不會改變,這個後面再講)

這個是最基本的引用案例(另外說句,由於A和B都指向了同一塊記憶體位址,所以經由B修改的內容,也能反映到A上面去)



详解Python浅拷贝、深拷贝及引用机制
#引用機制案例2
我們再來看一個案例

看題目貌似是會把元素2替換成本身這個列表,結果也許應該是A=[1,[1,2,3],3]

但其實不是! !你可以看到,紅框中部分,中間有無限多個嵌套

為什麼會這樣呢?

其實是因為,A指向的是[1,2,3]這個列表,在這個例子中,只是把A的第2個元素,指向了A對象本身,所以說,只是A的結構發生了變化!但是,A還是指向那個物件

我們可以透過印A的id來看,他的指向是沒有改變的! !



详解Python浅拷贝、深拷贝及引用机制 來看一下,A的指向並沒有改變



详解Python浅拷贝、深拷贝及引用机制 那如果我們要達到最後輸出效果是[ 1,[1,2,3],3]的效果,該如何來操作?

這裡,我們就要用到淺複製了,用法可以如下




详解Python浅拷贝、深拷贝及引用机制
#淺複製和深複製

#淺複製
#
現在,就來說說淺複製和深複製,上面的方法實際上只是進行了淺複製,shallow copy,含義是他是對原來引用的對象進行了複製,但是不再引用同一對像地址

看下面的例子,B透過B = A[:] 操作,來進行了淺複製,你可以看到,淺複製之後,A和B引用的記憶體位址已經是不同的了

但是,A和B內部的元素的引用位址,還是相同的,這點要非常注意!是有差別的! ! !

A和B的引用記憶體位址的不同,帶來的效果是,你在B上面進行的操作,並不會影響到A。

详解Python浅拷贝、深拷贝及引用机制

淺拷貝歸納:

#所以淺拷貝,可以歸納為,複製一份引用,新的物件和原來的物件的參考被區分開,但是內部元素的位址引用還是相同的

但是淺複製也會有問題,問題在哪裡呢?就是碰到有巢狀的情況,例如下面的情況可以看到,我給B賦值了一份A的淺複製,這樣A和B的id(記憶體位址)就不一樣了。

所以,當我修改A[0]=8的時候,B不會被影響到,因為他們A和B兩者是獨立的引用,但是這裡中間有一個嵌套的列表[4 ,5,6]

這個[4,5,6]我們可以理解為:A和B也共同引用著,也就是對於A和B的第二個元素來說,他們還是指向同一塊記憶體位址的。

另外要說一句,由於int是不可變型,所以,把A[0]修改成8之後,他的引用位址就變了!就和B[0]這個元素的引用區分開了。

详解Python浅拷贝、深拷贝及引用机制

深複製

那要如何面對這樣的情況呢?就要用到python模組裡面的copy模組了

copy模組有2個功能

1: copy.copy(你要複製的物件) : 這個是淺拷貝,和前面對list進行的[:] 操作性質是一樣的

2: copy.deepcopy(你要複製的物件) : 這個是深拷貝,他除了和淺拷貝一樣,會新產生一份物件的引用,另外對於內部的元素,都會新生成引用,以獨立分開.

看下面的例子,當你給B賦值一份A的深複製之後,他倆可以說是完全獨立開了,無論你修改的是A裡面的不可變元素,還是修改A裡面嵌套的可變元素,結果都不會影響到B

我的理解是:深複製可以稱之為遞歸拷貝,他會把所有嵌套的可變元素都拷貝一下,然後獨立引用出來.

详解Python浅拷贝、深拷贝及引用机制

#深複製歸納:

深複製的效果,除了和淺複製一樣,將物件的引用新生成一份引用之外,內部所有嵌套的元素,他都會幫你一一獨立開.

自己畫了2張圖,以表示淺複製和深複製的效果區別

需要說明的是,雖然淺複製之後,列表內不可變元素的引用地址還是相同的,但是,正因為他們是不可變元素,所以,在其中任意不可變元素被改變之後,引用位址都會是新的,而不會影響原來的引用位址。



详解Python浅拷贝、深拷贝及引用机制

详解Python浅拷贝、深拷贝及引用机制

#總結

所以,到這裡,淺複製和深複製的機制,基本上理解了。

另外還有特殊情況需要說明

對於不可變類型:int, str, tuple, float 這樣的元素來說,沒有拷貝這個說法,他們被修改之後,引用地址就是直接改變了,如下面

详解Python浅拷贝、深拷贝及引用机制

但是,如果不可變類型內部有嵌套的可變類型的時候,還是可以使用深複製的

#详解Python浅拷贝、深拷贝及引用机制

另外要提醒一句,平常我們用的最多的直接賦值(或可以說是直接傳遞引用)的方法,例如下面的例子

他是將a和b兩個可變元素同時指向一個記憶體位址,所以,任何改變都是波及到a和b的

详解Python浅拷贝、深拷贝及引用机制

#最後

可變類型:list , set , dict

不可變類型:int, str , float , tuple

淺複製方法:[:] , copy.copy() ,  使用工廠函數(list/dir/set )

深複製方法:copy.deepcopy()


以上是詳解Python淺拷貝、深拷貝及引用機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn