list和dict是python中常用的列表和字典。 這裡討論一下他們的原理及一些高階用法,供大家查詢參考。

list的切片

list的切片格式為:

list[start:end:step]

其中step如果沒有,則預設為1 下面舉例說明: 先定義一個list:

list = [1,2,3,4,5,6,7,8,9]

那麼list[1:6:2],就表示從1位置開始到3位置結束,間隔2,結果如下:

list[1:6:2]
[2, 4, 6]

注意:list的切片操作會返回一個新的列表,切片並不會改變原list本身。

dict的淺拷貝及深拷貝

什麼是dict(字典)的淺拷貝和深拷貝? 如果我們在已有指標的情況下,增加一個指標指向已經存在的記憶體,稱為淺拷貝。 如果我們增加一個指標的同時也新增了記憶體,並且新指標指向到新記憶體,稱為深拷貝。

注意區分dict的淺拷貝及深拷貝的區別。 下面舉個例子說明一下:

import copy   

dict = {"a":{"a1":"a123"}, "b":{"b1":"b123"}}
a_dict = dict.copy()                # 淺拷貝
a_dict["a"]["a1"]="newa123"

b_dict = copy.deepcopy(dict)        #深拷貝
b_dict["b"]["b1"]="newb123"

print(dict)
print(a_dict)
print(b_dict)

淺拷貝會替換掉原來的的dict,而深拷貝會新建一個dict,不會替換掉原來的dict。

list轉化為dict

在python3的環境下,list轉化成dict。 下面每一步都複製了執行語句後的結果。便於參考。 先定義一個list:

>>> list = ["a1", "b1"]

通過dict.fromkeys方法建立一個新字典:

>>> dict_list = dict.fromkeys(list,{"aa1":"1234556"})
>>> dict_list
{'a1': {'aa1': '1234556'}, 'b1': {'aa1': '1234556'}}

通過get方法獲取value的值,注意get的用法:

>>> value = dict_list.get("a1")
>>> value
{'aa1': '1234556'}

用update方法來修改dict:

>>> dict_list.update(a1="new123",b1="bbb123")
>>> dict_list
{'a1': 'new123', 'b1': 'bbb123'}

>>> dict_list.update((("a1","aaa123"),("b1","bbb123")))
>>> dict_list
{'a1': 'aaa123', 'b1': 'bbb123'} 

完整程式碼如下:

list = ["a1","b1"]
dict_list = dict.fromkeys(list,{"aa1":"1234556"})
value = dict_list.get("a1")
dict_list.update(a1="new123",b1="bbb123")
dict_list.update((("a1","aaa123"),("b1","bbb123")))

list和dict的實現原理

當我們定義一個list或者dict的時候,資料會存入記憶體中。 在list中隨著list資料的增大,查詢時間會增大, 而在dict中查詢元素的時候,不會隨著dict的增大而增大, 所以說dict查詢的效能遠大於list。

那麼,為什麼dict中查詢元素的時候,時間會比查詢list少呢? dict背後實現原理是雜湊表(hash),其特點是: 1、dict的key或者set的值,都必須是可以hash的, 就是說對不可變物件都是可hash的,比如str, fronzenset(不可變集合), tuple, 如果是自己實現的類,我們可以在類中加__hash__,這樣我們的物件就是可hash物件 2、dict的記憶體花銷大,但是查詢速度快,自定義物件或python內部的物件都是用dict包裝的。 3、dict的儲存順序和元素新增順序有關。 4、新增資料有可能改變已有資料的順序。 當表元(列表中的元素)小於三分之一,就會重新申請空間,在重新分配記憶體中,有可能重新改變順序。

字典與列表dict和list 字典通過偏移量直接定址。 查詢原理大致步驟如下,原諒我不會畫圖: 1、計算鍵的雜湊值(雜湊值)–> 2、使用雜湊值的一部分來定位散列表中的一個表元 --> 3、表元為空 --> 丟擲keyerror 4、表元不為空 --> 5、判斷鍵是否相等 --> 如果是 --> 返回表元裡的值 如果否,表示雜湊衝突 --> 6、使用雜湊值的另一部分來定位散列表中的另一行 --> 7、表元為空 --> 8、返回前面第3步驟繼續迴圈

set集合和frozenset集合

1、set集合是可變集合,可以新增,可以刪除。 set集合沒有hash值,它是無序的。不支援序列,不記錄元素的位置。 2、frozenset集合是不可變集合,不能新增,一旦建立就不能刪除。 frozenset可以作為字典的key,也可以作為其他集合的元素。

他們特點都是無序,不能重複的,如: set集合是無序且不能重複的:

>>> s=set('abcdcee')
>>> s
{'d', 'e', 'b', 'a', 'c'}

set集合可以新增:

>>> s.add('g')
>>> s
{'d', 'e', 'b', 'a', 'c', 'g'}

frozenset集合也是是無序且不能重複的:

>>> ss=frozenset('123332')
>>> ss
frozenset({'3', '1', '2'})

注意frozenset集合是沒有add方法的,也就是不能新增。

通過update來給set集合新增集合:

>>> ss=set('zyxzce')
>>> ss
{'e', 'z', 'y', 'x', 'c'}
>>> s.update(ss)
>>> s
{'d', 'e', 'z', 'y', 'b', 'x', 'a', 'c', 'g'}

difference可以檢視集合之間的區別:

>>> s.difference(ss)
{'g', 'b', 'a', 'd'}

list和dict的類繼承

list和dict本身也是類。 在我們定義類的時候,通常不建議繼承list和dict。 因為在某些情況之下,用c語言寫的dict,不會呼叫我們自定義的重新覆蓋的方法。 如果實在要繼承,可以用UserDict,如:

from collections import UserDict

class MyDict(UserDict):
    def __setitem__(self, key, value):
        super().__setitem__(key, value*2)
        
mydict = MyDict(a=1)

不建議的用法,這裡只是順帶一提。