Python---copy()、deepcopy()與賦值的區別

轉載自

首先直接上結論:

—–深複製,即將被複制對象完全再複製一遍作爲獨立的新個體單獨存在。所以改變原有被複制對象不會對已經複製出來的新對象產生影響。

—–而等於賦值,並不會產生一個獨立的對象單獨存在,他只是將原有的數據塊打上一個新標籤,所以當其中一個標籤被改變的時候,數據塊就會發生變化,另一個標籤也會隨之改變。

—–而淺複製要分兩種情況進行討論:

1)當淺複製的值是不可變對象(數值,字符串,元組)時和“等於賦值”的情況一樣,對象的id值與淺複製原來的值相同。

2)當淺複製的值是可變對象(列表和元組)時會產生一個“不是那麼獨立的對象”存在。有兩種情況:

第一種情況:複製的 對象中無 複雜 子對象,原來值的改變並不會影響淺複製的值,同時淺複製的值改變也並不會影響原來的值。原來值的id值與淺複製原來的值不同。

第二種情況:複製的對象中有 複雜 子對象 (例如列表中的一個子元素是一個列表),如果不改變其中複雜子對象,淺複製的值改變並不會影響原來的值。 但是改變原來的值 中的複雜子對象的值 會影響淺複製的值。

對於簡單的 object,例如不可變對象(數值,字符串,元組),用 shallow copy 和 deep copy 沒區別

複雜的 object, 如 list 中套着 list 的情況,shallow copy 中的 子list,並未從原 object 真的「獨立」出來。也就是說,如果你改變原 object 的子 list 中的一個元素,你的 copy 就會跟着一起變。這跟我們直覺上對「複製」的理解不同。

當淺複製的值是不可變對象(數值,字符串,元組)時,代碼如下:

>>> a="1234567"
>>> b=a
>>> id(a)
4367619440
>>> id(b)
4367619440
>>> c=copy.copy(a)
>>> id(c)
4367619440
>>> d=copy.deepcopy(a)
>>> id(d)
4367619440

當淺複製的值是可變對象(列表,字典)時,改變的值不是 複雜子對象 代碼如下:

>>> l1=[1,2,3]
>>> l2=l1
>>> l3=copy.copy(l1)
>>> id(l1)
4367654664
>>> id(l2)
4367654664
>>> id(l3)
4367628616
>>> l1.append(55)
>>> print(l1)
[1, 2, 3, 55]
>>> print(l3)
[1, 2, 3]

當淺複製的值是可變對象(列表,字典)時,改變的值是 複雜子對象 代碼如下:

>>> import copy
>>> list1=[1,2,['a','b']]
>>> list2=list1
>>> list3=copy.copy(list2)
>>> list4=copy.deepcopy(list3)
>>> id(list1)
4338414656
>>> id(list2)
4338414656
>>> id(list3)
4338415016
>>> id(list4)
4338414368
>>> list1[2].append('a')
>>> id(list1)
4338414656
>>> print list1
[1, 2, ['a', 'b', 'a']]
>>> print list3
[1, 2, ['a', 'b', 'a']]
>>> print list4
[1, 2, ['a', 'b']]
>>> list1.append(33)
>>> id(list1)
4338414656
>>> id(list3)
4338415016
>>> print list1
[1, 2, ['a', 'b', 'a'], 33]
>>> print list3
[1, 2, ['a', 'b', 'a']]

代碼說明:當改變 複雜子對象中的元素時,淺複製值發生了變化; 當改變的值不是複雜子對象,淺複製的值沒有發生變化。因爲 淺複製 ,複雜子對象的保存方式是 作爲 引用 方式存儲的,所以修改 淺複製的值 和原來的值都可以 改變 複雜子對象的值。

python的數據存儲方式
Python 存儲變量的方法跟其他 OOP 語言不同。它與其說是把值賦給變量,不如說是給變量建立了一個到具體值的 reference。

當在 Python 中 a = something 應該理解爲給 something 貼上了一個標籤 a。當再賦值給 a 的時候,就好象把 a 這個標籤從原來的 something 上拿下來,貼到其他對象上,建立新的 reference。 這就解釋了一些 Python 中可能遇到的詭異情況:

>> a = [1, 2, 3]
>>> b = a
>>> a = [4, 5, 6] //賦新的值給 a
>>> a
[4, 5, 6]
>>> b
[1, 2, 3]
# a 的值改變後,b 並沒有隨着 a 變

>>> a = [1, 2, 3]
>>> b = a
>>> a[0], a[1], a[2] = 4, 5, 6 //改變原來 list 中的元素
>>> a
[4, 5, 6]
>>> b
[4, 5, 6]
# a 的值改變後,b 隨着 a 變了

上面兩段代碼中,a 的值都發生了變化。區別在於,第一段代碼中是直接賦給了 a 新的值(從 [1, 2, 3] 變爲 [4, 5, 6]);而第二段則是把 list 中每個元素分別改變。而對 b 的影響則是不同的,一個沒有讓 b 的值發生改變,另一個變了。怎麼用上邊的道理來解釋這個詭異的不同呢?首次把 [1, 2, 3] 看成一個物品。a = [1, 2, 3] 就相當於給這個物品上貼上 a 這個標籤。而 b = a 就是給這個物品又貼上了一個 b 的標籤。

Python---copy()、deepcopy()與賦值的區別

第一種情況:a = [4, 5, 6] 就相當於把 a 標籤從 [1 ,2, 3] 上撕下來,貼到了 [4, 5, 6] 上。在這個過程中,[1, 2, 3] 這個物品並沒有消失。 b 自始至終都好好的貼在 [1, 2, 3] 上,既然這個 reference 也沒有改變過。 b 的值自然不變

Python---copy()、deepcopy()與賦值的區別

第二種情況:a[0], a[1], a[2] = 4, 5, 6 則是直接改變了 [1, 2, 3] 這個物品本身。把它內部的每一部分都重新改裝了一下。內部改裝完畢後,[1, 2, 3] 本身變成了 [4, 5, 6]。而在此過程當中,a 和 b 都沒有動,他們還貼在那個物品上。因此自然 a b 的值都變成了 [4, 5, 6]。

搞明白這個之後就要問了,對於一個複雜對象的淺copy,在copy的時候到底發生了什麼?
再看一段代碼:

>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 裏邊有三個元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False 
#cop1 和 cop2 看上去相同,但已不再是同一個object
>>> origin[2][0] = "hey!" 
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin內的子list [3, 4] 改掉了一個元素,觀察 cop1 和 cop2

學過docker的人應該對鏡像這個概念不陌生,我們可以把鏡像的概念套用在copy上面。

copy概念圖如下:

Python---copy()、deepcopy()與賦值的區別

copy(淺複製)對於一個複雜對象的子對象並不會完全複製,什麼是複雜對象的子對象呢?就比如序列裏的嵌套序列,字典裏的嵌套序列等都是複雜對象的子對象。對於子對象,python會把它當作一個公共鏡像存儲起來,所有對他的複製都被當成一個引用,所以說當其中一個引用將鏡像改變了之後另一個引用使用鏡像的時候鏡像已經被改變了。

所以說看這裏的origin[2],也就是 [3, 4] 這個 list。根據 shallow copy 的定義,在 cop1[2] 指向的是同一個 list [3, 4]。那麼,如果這裏我們改變了這個 list,就會導致 origin 和 cop1 同時改變。這就是爲什麼上邊 origin[2][0] = “hey!” 之後,cop1 也隨之變成了 [1, 2, [‘hey!’, 4]]。

deepcopy概念圖如下:
Python---copy()、deepcopy()與賦值的區別

deepcopy的時候會將複雜對象的每一層複製一個單獨的個體出來。
這時候的 origin[2] 和 cop2[2] 雖然值都等於 [3, 4],但已經不是同一個 list了。即我們尋常意義上的複製。

參考文章 1)http://iaman.actor/blog/2016/04/17/copy-in-python
2)https://blog.csdn.net/u011630575/article/details/78604226

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