自底向上:從可變對象、不可變對象到深淺拷貝再到數據結構
- 2022 年 4 月 13 日
- 筆記
一、不可變對象和可變對象**
Python 在 heap 中分配的對象分成兩類:可變對象和不可變對象。所謂可變對象是指,對象的內容是可變的,例如 list。而不可變的對象則相反,表示其內容不可變。
不可變對象 :int,string,float,tuple -- 可理解為C中,該參數為值傳遞
可變對象 :list,dictionary -- 可理解為C中,該參數為指針傳遞
不可變對象
由於Python中的變量存放的是對象引用,所以對於不可變對象而言,儘管對象本身不可變,但變量的對象引用是可變的。運用這樣的機制,有時候會讓人產生糊塗,似乎可變對象變化了。如下面的代碼:
for i in range(10):
print(id(i))
outputs:
2221403367696
2221403367728
2221403367760
2221403367792
2221403367824
2221403367856
2221403367888
2221403367920
2221403367952
2221403367984
從上面得知,不可變的對象的特徵沒有變,依然是不可變對象,變的只是創建了新對象,改變了變量的對象引用。
對於可變對象
其對象的內容是可以變化的。當對象的內容發生變化時,變量的對象引用是不會變化的。如下面的例子。
m = [1,2,3]
print(id(m))
m += [4]
print(id(m))
outputs:
2221492412544
2221492412544
對於可變對象 list這種進行操作時,相當於修改對象中某個屬性,所以不會改變地址 python都是將對象的引用(內存地址)賦值給變量的。
現在問題來了
def myfunc(l):
l.append(1) # 列表加1
print(l)
l = [1,2,3]
myfunc(l) # [1,2,3,1]
print(l) # [1,2,3,1]
可變對象l
送進函數中操作後,對函數外面的也起作用,不可變對象則相反。因為函數直接對可變對象的地址的值進行了修改。該操作對於類初始化傳值有一樣的效果。
只有搞懂可變對象和不可變對象之後,才能理解下面的深拷貝與淺拷貝。
二、深拷貝和淺拷貝
淺拷貝
- 淺拷貝會創建一個新的容器對象(compound object)
- 對於對象中的元素,淺拷貝就只會使用原始元素的引用(內存地址)
深拷貝
- 深拷貝和淺拷貝一樣,都會創建一個新的容器對象(compound object)
- 和淺拷貝的不同點在於,深拷貝對於對象中的元素,深拷貝都會重新生成一個新的對象
更進一步,由於淺拷貝複製的是元素的地址引用,如果元素是不可變類型,修改就更新了地址,和原對象的地址不同了,所以原對象不會受到影響,當元素是可變類型,修改沒有改變地址,這樣原對象也就跟着變化。
對於深拷貝而言,改變任何一個對象都對另一個沒有影響,它們是獨立的。無論是不可變對象,還是可變對象,深拷貝後它們的地址已經不一樣了。
三、哈希表中的可哈希和不可哈希對象
了解了上文之後,在python中使用哈希表(散列表)數據結構時,又有可哈希和不可哈希對象之分,其實這裡就是可變對象和不可變對象。為什麼呢?
對於不可變類型而言,不同的值意味着不同的內存,相同的值存儲在相同的內存,如果將我們的不可變對象理解成哈希表中的 Key,將內存理解為經過哈希運算的哈希值 Value,這不正好滿足哈希表的性質嘛。
對於可變對象而言,比如一個列表,更改列表的值,但是對象的地址本身是不變的,也就是說不同的 Key,映射到了相同的 Value,這顯然是不符合哈希值的特性的,即出現了哈希運算裏面的衝突。
所以在python中使用set()、dictionary()時鍵值都必須是不可變對象。
參考文章:Python基礎:Python可變對象和不可變對象