最近看了本書《你也能看得懂的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
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