複製列表—賦值、深淺拷貝與切片拷貝

最近看了本書《你也能看得懂的Python算法書》,覺得不錯,分享下學習心得~
這裏是第一章~
Python中列表存儲的方法和其他語言中的不太一樣,列表中的元素在計算機的存儲空間中佔據一定的內存,而列表本身存儲的是這些元素的存儲地址,在調用列表元素的時候根據地址來調出它們原本的值。

1、賦值操作

如果直接給新的列表賦值,只是複製了原來列表存儲的地址,所以元素本身並沒有被複製成兩份
賦值操作:

>>> numbers=[1,2,3,4,5,6]
>>> copylist=numbers
>>> id(numbers)
2644503419976
>>> id(copylist)
2644503419976

可以看出numbers和copylist共享一個ID地址
複製列表

2、Copy函數

Copy函數返回的是複製原列表中的元素後產生的一組新元素的存儲地址,其被存儲在新的列表中,這樣,修改複製後的列表中的元素就不會影響原來的列表

>>> numbers=[1,2,3,4,5,6]
>>> id(numbers)
2644503419976
>>> copylist2=numbers.copy()
>>> id(copylist2)
2644503354968
>>>

可以看出copy函數給copylist2開闢了一個新ID
copy函數效果

3、切片複製

在這本書的第三章裏,切片複製被說成是深拷貝!經調試,真不是!
在這裏插入圖片描述
來看看我的調試代碼

a=[1,'two',[3,4]]
b=a[:]
print(a)
print()
a[0]=5
a[2][0]=6
print(a,b,sep='\n')

運行結果:

[1, 'two', [3, 4]]

[5, 'two', [6, 4]]
[1, 'two', [6, 4]]

列表b還是變了!
也就是說,對於嵌套列表,它和copy函數一樣,是淺拷貝效果!
爲什麼呢?來看下ID

>>> id(a)
2468216504840
>>> id(b)
2468216504712
>>> id(a[2])
2468216504392
>>> id(b[2])
2468216504392
>>> id(a[0])
1497459456
>>> id(b[0])
1497459456
>>>

由上可以看出:id(a)!=id(b),id(a[0])=id(b[0]),id(a[1])=id(b[1])
所以,b對於a確實是開闢了新的ID,但對於列表裏的元素,列表僅僅是引用了這些元素。
網上找了張很形象的圖:
在這裏插入圖片描述

4、deepcopy函數

要想實現a和b其父對象和子對象均完全獨立,可以使用deepcopy函數,即深拷貝。
在這裏插入圖片描述

import copy
a=[1,'two',[3,4]]
b=copy.deepcopy(a)
print('a[0]的內存地址:',id(a[0]))
print('b[0]的內存地址:',id(b[0]))
print('a[2]的內存地址:',id(a[2]))
print('b[2]的內存地址:',id(b[2]))
print()
a[0]=5
a[2][0]=6
print('子對象改變後:')
print('a[0]的內存地址:',id(a[0]))
print('b[0]的內存地址:',id(b[0]))
print('a[2]的內存地址:',id(a[2]))
print('b[2]的內存地址:',id(b[2]))

運行結果:

a[0]的內存地址: 1478322944
b[0]的內存地址: 1478322944
a[2]的內存地址: 2592844271304
b[2]的內存地址: 2592844271496

子對象改變後:
a[0]的內存地址: 1478323072
b[0]的內存地址: 1478322944
a[2]的內存地址: 2592844271304
b[2]的內存地址: 2592844271496

對比看出,深拷貝後a[2]和b[2]的內存地址是不一樣的,且當改變a[0]時,a[0]的內存地址變,b[0]不變!父對象與子對象完全獨立!
現在可以總結如下:
1)b = a: 賦值引用,a 和 b 都指向同一個對象。
2)b = a.copy(): 淺拷貝, a 和 b 是一個獨立的對象,但他們的子對象還是指向統一對象(是引用)。
3)b = copy.deepcopy(a): 深度拷貝, a 和 b 完全拷貝了父對象及其子對象,兩者是完全獨立的。

接下來,我打算用淺拷貝來探索下列表~

主要會使用id() 函數來獲取對象的內存地址。

a=[1,'two',[3,4]]
b=a.copy()
print('a[0]的內存地址:',id(a[0]))
print('b[0]的內存地址:',id(b[0]))
print('a[2]的內存地址:',id(a[2]))
print('b[2]的內存地址:',id(b[2]))
print()
a[0]=5
a[2][0]=6
print('子對象改變後:')
print('a[0]的內存地址:',id(a[0]))
print('b[0]的內存地址:',id(b[0]))
print('a[2]的內存地址:',id(a[2]))
print('b[2]的內存地址:',id(b[2]))

運行結果:

a[0]的內存地址: 1478322944
b[0]的內存地址: 1478322944
a[2]的內存地址: 2592843820168
b[2]的內存地址: 2592843820168

子對象改變後:
a[0]的內存地址: 1478323072
b[0]的內存地址: 1478322944
a[2]的內存地址: 2592843820168
b[2]的內存地址: 2592843820168

在a=[1,‘two’,[3,4]]中,a[0]存儲的對象爲不可變對象即數值類型int ,對於不可變對象,變量對應內存的值不允許被改變。當變量要改變時(即進行a[0]=5操作時),實際上是把原來的值複製一份後再改變,開闢一個新的地址,所以子對象改變前後,a[0]的內存地址變了;
a[2]存儲對象爲可變對象list,可變對象由於所指對象可以被修改,所以無需複製一份之後再改變,直接原地改變,所以不會開闢新的內存,改變前後id不變。

最後來道面試題:求a,b,c,d,e的值

import copy
a=[1,'two',[3,4]]
b=a
c=copy.copy(a)
d=copy.deepcopy(a)
e=a[:]
print('修改前原列表',a)
print()
a[0]=5
a[2][0]=6
print('原列表      ',a)
print('賦值列表    ',b)
print('淺拷貝列表  ',c)
print('深拷貝列表  ',d)
print('切片拷貝列表',e)

結果:

修改前原列表 [1, 'two', [3, 4]]

原列表       [5, 'two', [6, 4]]
賦值列表     [5, 'two', [6, 4]]
淺拷貝列表   [1, 'two', [6, 4]]
深拷貝列表   [1, 'two', [3, 4]]
切片拷貝列表 [1, 'two', [6, 4]]

本章其他函數整理:

功能 函數 語法/格式 效果/返回值
排序 sort List.sort() 對List進行永久性從小到大排序
reserve List.reserve() 對List永久性反轉
最值 max max(List) 返回表中最大值(數值型)
min min(List) 返回表中最小值(數值型)
統計 count List.count(item) 返回表中item元素出現的個數(數值型)
索引 index List.index(item) 返回表中item元素位置索引(數值型)
清空 clear List.clear() 返回空列表

參考文獻:
[1]王碩.你也能看得懂的 Python算法書[M].北京:電子工業出版社,2018.30-35
[2]Python中可變對象和不可變對象 - 尼古拉斯特侖蘇 - 博客園 https://www.cnblogs.com/suxianglun/p/9013021.html
[3]Python 直接賦值、淺拷貝和深度拷貝解析 | 菜鳥教程 https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html

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