1. 程式人生 > 其它 >python可變物件和不可變物件

python可變物件和不可變物件

不可變物件:tuple,str,int

可變物件:list、dict

dict

請務必注意,dict內部存放的順序和key放入的順序是沒有關係的。

和list比較,dict有以下幾個特點:

查詢和插入的速度極快,不會隨著key的增加而變慢;
需要佔用大量的記憶體,記憶體浪費多。
而list相反:

查詢和插入的時間隨著元素的增加而增加;
佔用空間小,浪費記憶體很少
dict是用空間來換取時間的一種方法。

dict可以用在需要高速查詢的很多地方,在Python程式碼中幾乎無處不在,正確使用dict非常重要,需要牢記的第一條就是dict的key必須是不可變物件。

這是因為dict根據key來計算value的儲存位置,如果每次計算相同的key得出的結果不同,那dict內部就完全混亂了。這個通過key計算位置的演算法稱為雜湊演算法(Hash)。

要保證hash的正確性,作為key的物件就不能變。在Python中,字串、整數等都是不可變的,因此,可以放心地作為key。而list是可變的,就不能作為key:

set

set和dict類似,也是一組key的集合,但不儲存value。由於key不能重複,所以,在set中,沒有重複的key。

s = set([1, 1, 2, 2, 3, 3])
print(s) #{1,2,3}
s.add(5) #增加元素
print(s)  #{1,2,3,5}
s.remove(5) #刪除元素
print(s)  #{1,2,3}

要建立一個set,需要提供一個list作為輸入集合:

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

set和dict都是無序的,的唯一區別僅在於沒有儲存對應的value,但是,set的原理和dict一樣,所以,同樣不可以放入可變物件,因為無法判斷兩個可變物件是否相等,也就無法保證set內部“不會有重複元素”。

tuple:
談談pop()和remove()2個方法的用法,前者是根據索引刪除,後者是根據元素。

所以類似dict和set等無序的用remove()方法刪除
list和tuple有序的用pop()方法刪除

tuple和list一樣都是一種有序列表
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
這個tuple定義的時候有3個元素,分別是'a','b'和一個list。不是說tuple一旦定義後就不可變了嗎?怎麼後來又變了?

別急,我們先看看定義的時候tuple包含的3個元素:

當我們把list的元素'A'和'B'修改為'X'和'Y'後,tuple變為:

表面上看,tuple的元素確實變了,但其實變的不是tuple的元素,而是list的元素。tuple一開始指向的list並沒有改成別的list,所以,tuple所謂的“不變”是說,tuple的每個元素,指向永遠不變。即指向'a',就不能改成指向'b',指向一個list,就不能改成指向其他物件,但指向的這個list本身是可變的!

理解了“指向不變”後,要建立一個內容也不變的tuple怎麼做?那就必須保證tuple的每一個元素本身也不能變。

上面我們講了,str是不變物件,而list是可變物件。

對於可變物件,比如list,對list進行操作,list內部的內容是會變化的,比如:

>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而對於不可變物件,比如str,對str進行操作呢:

>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

雖然字串有個replace()方法,也確實變出了'Abc',但變數a最後仍是'abc',應該怎麼理解呢?

我們先把程式碼改成下面這樣:

>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

要始終牢記的是,a是變數,而'abc'才是字串物件!有些時候,我們經常說,物件a的內容是'abc',但其實是指,a本身是一個變數,它指向的物件的內容才是'abc':

當我們呼叫a.replace('a', 'A')時,實際上呼叫方法replace是作用在字串物件'abc'上的,而這個方法雖然名字叫replace,但卻沒有改變字串'abc'的內容。相反,replace方法建立了一個新字串'Abc'並返回,如果我們用變數b指向該新字串,就容易理解了,變數a仍指向原有的字串'abc',但變數b卻指向新字串'Abc'了:

所以,對於不變物件來說,呼叫物件自身的任意方法,也不會改變該物件自身的內容。相反,這些方法會建立新的物件並返回,這樣,就保證了不可變物件本身永遠是不可變的。

番外篇:

要理解dict的有關內容需要你理解雜湊表(map)的相關基礎知識,這個其實是《演算法與資料結構》裡面的內容。

1.list和tuple其實是用連結串列順序儲存的,也就是前一個元素中儲存了下一個元素的位置,這樣只要找到第一個元素的位置就可以順藤摸瓜找到所有元素的位置,所以list的名字其實就是個指標,指向list的第一個元素的位置。list的插入和刪除等可以直接用連結串列的方式進行,比如我要在第1個元素和第2個元素中間插入一個元素,那麼直接在連結串列的最後面(我們假設這個list只有兩個元素,那麼也就是在第3個元素的位置上)插入這個元素,然後把第一個元素指標指向這個元素(第3個位置),然後再把新插入的元素的指標指向原來的第2個元素,這樣插入操作就完成了。讀取這個list的時候,先用list的名字(就是個指標,指向第1個元素的位置)找到第一個元素,然後用第1一個元素的指標找到第2個元素(位置3),然後用第2個元素的指標找到第3個元素(位置2),以此類推。所以list的順序和記憶體中的實際順序其實不一定完全對應。這種儲存方式不會浪費記憶體,但查詢起來特別費時間,因為要按照連結串列一個一個找下去,如果你的list特別大的話,那麼要等好久才會找到結果。

2.dict則為了快速查詢使用了一種特別的方法,雜湊表。雜湊表採用雜湊函式從key計算得到一個數字(雜湊函式有個特點:對於不同的key,有很大的概率得到的雜湊值也不同),然後直接把value儲存到這個數字所對應的地址上,比如key='ABC',value=10,經過雜湊函式得到key對應的雜湊值為123,那麼就申請一個有1000個地址(從0到999)的記憶體,然後把10存放在地址為123的地方。類似的,對於key='BCD',value=20,得到key的雜湊值為234,那麼就把20存放在地址為234的地方。對於這樣的表查詢起來是非常方便的。只要給出key,計算得到雜湊值,然後直接到對應的地址去找value就可以了。無論有幾個元素,都可以直接找到value,無需遍歷整個表。不過雖然dict查詢速度快,但記憶體浪費嚴重,你看我們只儲存了兩個元素,都要申請一個長度為1000的記憶體。

3.現在你知道為啥key要用不可變物件了吧?因為不可變物件是常量,每次的雜湊值算出來都是固定的,這樣就不會出錯。比如key='ABC',value=10,儲存地址為123,假設我突發奇想,把key改成'BCD',那麼當查詢'BCD'的value的時候就會去234的地址找,但那裡啥也沒有,這就亂套了。

3.你看我們上面有一句話:對於不同的key,有很大的概率得到的雜湊值也不同。那麼有很小的概率不同的key可以得到相同的雜湊值了?沒錯,比如對於我們的例子來說,雜湊值只有3位,那麼只要元素個數超過1000,就一定會有至少兩個key的雜湊值相同(鴿籠原理),這種情況叫“衝突”,設計雜湊表的時候要採取辦法減少衝突,實在衝突了也要想辦法補救。不過這是編譯器的事情,況且對於初學者的我們來說碰到的衝突的概率基本等於零,就不用操心了。

本文來自部落格園,作者:qxll,轉載請註明原文連結:https://www.cnblogs.com/qxll/p/15380327.html