???? “Python貓” ,一個值得加星標的公衆號
劇照 | 《霸王別姬》
作者:oldPanda
來源:https://old-panda.com/2020/02/09/python-ordereddict
現象
根據哈希表的定義,以及之前簡單實現過的一個字典數據結構,當 Key 被插入哈希表後,哈希表根據散列函數求出的值來安排這個 Key 所在的位置,所以當我們遍歷哈希表的時候, Key 的順序是不確定的,因此碼農在使用哈希表這個數據結構的時候,是不應該依賴於 Key 的插入順序來達到某些目的的。
但有的時候我們可能會對哈希表中 Key 的插入順序感興趣,這時有經驗的 Python 工程師就會用 collections
中的 OrderedDict
來保持插入 Key 的順序。
>>> d1 = {}
>>> d1['a'] = 1
>>> d1['b'] = 2
>>> d1['c'] = 3
>>> d1['d'] = 4
>>> d1['e'] = 5
>>> d1
{'b': 2, 'd': 4, 'c': 3, 'a': 1, 'e': 5}
>>> from collections import OrderedDict
>>> d2 = OrderedDict()
>>> d2['a'] = 1
>>> d2['b'] = 2
>>> d2['c'] = 3
>>> d2['d'] = 4
>>> d2['e'] = 5
>>> d2
OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)])
>>>
d1
是一個普通的字典實例,我們按照 abcde 的順序插入 Key 之後,打印 d1 顯示的是亂序的,這符合我們對哈希表的理解, d2
是一個 OrderedDict
實例,同樣按 abcde 的順序插入,從輸出結果我們能看到它記憶了插入的順序。那麼問題來了,它是如何做到的?
原理
還是去源碼中一探究竟,找到 Python 3 的 OrderedDict 實現。
不難發現,在繼承 了自身的 dict 之後,類中還聲明瞭一個循環雙向鏈表 self.__root
作爲自己的屬性,用另一個 dict 結構 self.__map
來存儲 Key 到鏈表節點的對應關係,這樣在更新或刪除一個已有 Key 的時候,能在常數時間內完成對雙向鏈表的操作。
這幾個數據結構的關係如下所示,注意 self.__root
總是指向雙向鏈表的頭部。
這樣在遍歷 OrderedDict
的時候,直接過一遍循環雙向鏈表即可。
但類變得複雜了,相應的操作也會複雜起來,但上述的兩種數據結構的結合,仍然能夠做到對字典示例常數級操作。查詢( __getitem__
)操作比較直觀,可以通過調用父類的函數來完成。下面重點記錄插入和刪除操作是如何進行的。
插入
插入操作調用魔術方法 __setitem__
,當 Key 存在時,直接調用父類函數,複雜的情況是當 Key 不存在的時候,不僅需要把鍵值對放到哈希表中,還要同步更新 self.__map
和 self.__root
。
初始化新 Key 的鏈表節點
找到雙向鏈表的尾端,因爲是循環鏈表,所以可以直接通過
self.__root.prev
得到將新的鏈表節點放到雙向鏈表的尾端
調用父類的插入函數處理新的鍵值對
刪除
刪除操作調用 __delitem__
調用父類的刪除函數刪掉給定的 Key
通過
self.__map
定位到將要刪除的 Key 對應的鏈表節點從鏈表中將這個節點刪除
顯然,關於有順序的哈希表,核心是對循環雙向鏈表的操作,而鏈表的插入刪除都是常數級操作,又用了一個 dict
來保證查詢的常數級操作,所以既能記錄 Key 插入的順序,又能保證操作的時間複雜度。
但是。。。
在 Python 3.7 的發佈中,官宣了 Python 原生的 dict
就能保證 Key 的插入順序。
The insertion-order preservation nature of dict objects is now an official part of the Python language spec.
這是否就意味着 OrderedDict
變得多餘了呢?StackOverflow 上有個討論列出了 Python 3.7 中原生 dict
與 OrderedDict
的區別,主要有兩條。
OrderedDict 支持對 Key 的順序有關的操作,比如說把某個 Key 挪到頭部或者尾部,逆序給出 Key 的序列(這個特性在 Python 3.8 中加到了原生
dict
中)等OrderedDict 比較的時候會把 Key 的插入順序考慮進去,而 dict 不會,比如說
>>> from collections import OrderedDict
>>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)])
False
>>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)])
True
優質文章,推薦閱讀:
感謝創作者的好文