與很多程式語言一樣,python的拷貝方式分為賦值、淺拷貝、深拷貝。
在學習過程中,我對拷貝的了解很模糊。在經過一系列的實驗後,我對這三者的概念有了進一步的理解。
首先,我們要對賦值操作有以下認識:
我們通過一些例子來分析下賦值操作:
1.1 字串賦值
a = 'hello'
b = 'hello'
c = a
print(a, b, c)
# hello hello hello
[id(x) for x in [a,b,c]]
# [4526716328, 4526716328, 4526716328]
我們可以發現a, b, c三者的id是一樣的,以上操作相當於c = a = b = 『hello』,為什麼呢?因為str是不可變型別,所以』hello』只有乙個記憶體位址,a, b, c三者的id一樣。賦值是系統先給乙個變數或者物件(這裡是』hello』)分配了記憶體,然後再將id賦給a, b, c,所以它們的id是相同的。
a = 'world'
print(a, b, c)
# world hello hello
[id(x) for x in [a,b,c]]
# [4526651464, 4526716328, 4526716328]
這時a的id和值變了,但是b, c的id和值都未變。因為str是不可變型別,不同str占用不同的記憶體空間。重新賦值a需要新開闢記憶體空間,再把a指向新的str記憶體位址,所以當a的str值改變,a指向的記憶體位址也會改變。b, c由於字串』hello』的不變性,所以id和值都不會發生改變。
1.2 列表賦值
a = ['hello']
b = ['hello']
c = a
print(a, b, c)
# ['hello'] ['hello'] ['hello']
[id(x) for x in [a, b, c]]
# [4535290824, 4535290888, 4535290824]
為何a和b的id不同?因為list是可變型別,任意乙個list占用不同的記憶體位址。
print(a, b, c)
# ['hello', 12] ['hello'] ['hello', 12]
[id(x) for x in [a,b,c]]
# [4535290824, 4535290888, 4535290824]
a和c的值均改變,id沒有改變;變數b的id和值都沒有改變。由於list的可變性,所以修改list的值不需要另外開闢空間,只需修改原位址的值,所以a, c的值均改變,id沒有改變。
對於數字,字串和其他原子型別物件等,沒有被拷貝的說法。即便是用深拷貝,檢視id也是一樣的;如果對其重新賦值,也只是新建立乙個物件,替換掉舊的而已。
所謂淺拷貝就是對引用的拷貝。
2.1 利用切片操作和工廠方法(list方法)拷貝
**場景:有乙個小伙jack,tom通過切片操作拷貝jack,anny通過工廠方法拷貝jack。
jack = ['jack', ['age', 20]]
tom = jack[:]
anny = list(jack)
print(id(jack), id(tom), id(anny))
# 4570999880 4570944008 4570999752
從id來看,三者是不同的物件。
2.2 修改原物件的值
tom[0] = 'tom'
anny[0] = 'anny'
anny[1][1] = 18
print(jack, tom, anny)
# ['jack', ['age', 18]] ['tom', ['age', 18]] ['anny', ['age', 18]]
print(id(jack), id(tom), id(anny))
# 4570999880 4570944008 4570999752
奇怪的事情發生了,jack、tom、anny的年齡都變成18了。jack、tom、anny他們應當都是不同的物件,怎麼會互相影響呢?看下jack,tom,anny的內部元素每個元素id:
print([id(x) for x in jack])
# [4570490672, 4571499784]
print([id(x) for x in tom])
# [4570491232, 4571499784]
print([id(x) for x in anny])
# [4570490616, 4571499784]
恍然大悟,原來jack、tom、anny的年齡指向的是同乙個list元素,修改了其中乙個,當然影響其他人了。為了便於理解,我畫了乙個草圖:
利用切片操作和工廠方法(list方法)拷貝叫淺拷貝。它只是拷貝了最外圍的物件本身,內部的元素都只是拷貝了乙個引用而已。
利用copy中的deepcopy方法進行拷貝就叫做深拷貝,外圍和內部元素都進行了拷貝物件本身,而不是引用。它是完全拷貝了乙個副本,變數內部元素id都不一樣。
3.1 copy.deepcopy
我們利用copy模組中的deepcopy方法進行深拷貝。
import copy
jack = ['jack', ['age', '20']]
tom = copy.deepcopy(jack)
anny = copy.deepcopy(jack)
print([id(x) for x in [jack, tom, anny]])
# [4526691976, 4526690440, 4526692936]。因為list是可變型別,所以jack, tom, anny的id不同
print([id(x) for x in jack])
# [4526653368, 4526690568],'jack'是str不可變型別,['age','20']是list可變型別
print([id(x) for x in tom])
# [4526653368, 4526644488]
print([id(x) for x in anny])
# [4526653368, 4526642120]
深拷貝後,可以發現jack, tom,anny的id以及元素值均不相同。這才是完全拷貝了乙個副本 。
3.2 修改原資料
tom[0] = 'tom'
anny[0] = 'anny'
print(jack, tom, anny)
# ['jack', ['age', '20']] ['tom', ['age', '20']] ['anny', ['age',20]]
anny[1][1] =18
(24)
print(jack, tom, anny)
# ['jack', ['age', '20']] ['tom', ['age', '20'
,24]] ['anny', ['age',18]]
修改jack, tom,anny,它們之間不會互相影響。列印出每個人的內部元素id。
print([id(x) for x in [jack, tom, anny]])
# [4526691976, 4526690440, 4526692936]
print([id(x) for x in jack])
# [139132064, 3073507244l]
print([id(x) for x in tom])
# [139137464, 139132204]
print([id(x) for x in anny])
# [139141632, 139157548]
深拷貝後,jack,tom,anny元素id均不同,內部元素也都指向了不同的物件。這是完全拷貝的乙個副本,修改jack後,tom沒有發生改變,因為tom是乙個完全的副本,元素id與jack均不同,jack修改不影響tom。
賦值是把乙個物件記憶體位址賦給乙個變數,讓變數指向該記憶體位址( 舊瓶裝舊酒 )
淺拷貝是在另一塊位址中建立乙個新的變數或容器,但是容器內的元素的位址均是源物件的元素的位址的拷貝。也就是說新的容器中指向了舊的元素( 新瓶裝舊酒 )
深拷貝是在另一塊位址中建立乙個新的變數或容器,同時容器內的元素的位址也是新開闢的,僅僅是值相同而已,是完全的副本。也就是說( 新瓶裝新酒 )
python中的賦值、淺拷貝、深拷貝介紹
js 淺拷貝直接賦值 js的賦值與淺拷貝 深拷貝
昨天翻了下陣列api,看到concat和slice方法,突然想到這個兩個方法是淺拷貝還是深拷貝,結果陷入了死胡同,為什麼mdn文件說是淺拷貝,但進行簡單的操作為什麼能複製成功啊,糾結半天後才弄清原由,原來我一直把賦值和深淺拷貝搞混了。首先不要把引用型別的賦值歸結為淺拷貝,深拷貝和淺拷貝只針對像 ob...
python學習筆記番外 linux檔案拷貝程式
import os import shutil import sys def copydir orignaldir,destdir if not os.path.exists destdir os.mkdir destdir 獲取原目錄下的檔案和目錄,進行複製 orignaldirlist os.l...
python的深賦值與淺賦值
原文 在python中,物件賦值實際上是物件的的引用,當建立乙個物件,然後把它賦值給另外乙個變數的時候,python沒有拷貝這個物件,而只是拷貝了這個物件的引用,所以就出現了淺複製,即複製後原物件改變後,複製出來的物件也會改變,要防止複製出來的物件改變,就要使用深複製 python複製三種方式 1 ...