在閱讀《流暢的python》以及《深刻理解python特性》時發現這兩本書都說起了python的一個庫:collection.namedtuple,這個庫能夠快速的建立一個tuple對象而且能夠爲其命令,並且輸出查看該對象值時,tuple中是以key=value的形式存儲,方便了用戶的使用,最主要的是:這個對象在內存中所消耗的字節數與普通tuple同樣!python
>>> from collections import namedtuple >>> Card = namedtuple('Card',"rank suit") >>> Card <class '__main__.Card'> >>> type(Card) <class 'type'> >>> ranks = [str(n) for n in range(2,11)] +list("JQKA") >>> suits = 'spades diamonds clubs hearts' >>> cards = [Card(rank,suit) for rank in ranks for suit in suits] >>> cards [Card(rank='2', suit='s'), Card(rank='2', suit='p'), Card(rank='2', suit='a'), Card(rank='2', suit='d'), Card(rank='2', suit='e'), Card(rank='2', suit='s'), Card(rank='2', suit=' ') 後面省略
能夠發現使用了namedtupe後,每一個tuple有了名稱,並且存儲格式爲key=value的形式。算法
同時快速建立牌組的列表的推到式使用於笛卡爾積這樣的形式,快速的構建list,不管是一個變量,仍是變量;數組
關於tuple數據結構
都知道tuple中存儲的是不能輕易修改的變量值,那麼下面這段代碼修改後會發現什麼狀況呢?函數
>>> t = (1,2,[30,40]) >>> t[2] [30, 40] >>> t[2]+=[50,60]
狀況1:因爲tuple存儲的是不可修改的變量,修改會發生錯誤;學習
狀況2:因爲list是可變對象,能夠修改爲功;並且沒有什麼錯誤提示;優化
惋惜的是,修改後既提示了錯誤,也讓用戶修改了其中可變對象的值ui
>>> t = (1,2,[30,40]) >>> t[2] [30, 40] >>> t[2]+=[50,60] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> t (1, 2, [30, 40, 50, 60])
關於list spa
最近學習list的時候發現一個有趣的現象,那就是我切片的時候,切片範圍比賦予的值多時,發現竟然沒有賦值的那一塊部分的值竟然消失了,留下代碼記錄一下:code
>>> a = [1,2,3,4,5,6,7] >>> a[2:5]=[33,44] >>> a [1, 2, 33, 44, 6, 7]
能夠說切片功能十分強大,能夠對序列進行嫁接、切除或就地修改操做。
關於dict
dict能夠說是python中最強大的數據結構,由於他能夠很方便的經過key值查到value;可是它花費的代價倒是很大的,相比於list來講;
>>> import sys >>> a = set() >>> b = {} >>> sys.getsizeof(a) 232 >>> sys.getsizeof(b) 248 >>> c = [] >>> sys.getsizeof(c) 72 >>> d = () >>> sys.getsizeof(d) 56
能夠發現一個字典要消耗248字節,集合要消耗232字節,list只須要72字節,而tuple只須要花費56字節!
關於dict的底層實現,下邊是書中的話語,已經解釋的十分詳細,無需我再添油加醋;
散列表實際上是一個稀疏數組(老是有空白元素的數組稱爲稀疏數組)。 在通常的數據結構教材中,散列表裏的單元一般叫做表元(bucket)。 在 dict 的散列表當中,每一個鍵值對都佔用一個表元,每一個表元都有兩 個部分,一個是對鍵的引用,另外一個是對值的引用。由於全部表元的大 小一致,因此能夠經過偏移量來讀取某個表元。
由於 Python 會設法保證大概還有三分之一的表元是空的,因此在快要達 到這個閾值的時候,原有的散列表會被複制到一個更大的空間裏面。
若是要把一個對象放入散列表,那麼首先要計算這個元素鍵的散列值。 Python 中能夠用 hash() 方法來作這件事情,接下來會介紹這一點。
- 散列值和相等性
內置的 hash() 方法能夠用於全部的內置類型對象。若是是自定義 對象調用 hash() 的話,實際上運行的是自定義的 __hash__。如 果兩個對象在比較的時候是相等的,那它們的散列值必須相等,否 則散列表就不能正常運行了。例如,若是 1 == 1.0 爲真,那麼 hash(1) == hash(1.0) 也必須爲真,但其實這兩個數字(整型 和浮點)的內部結構是徹底不同的。
爲了讓散列值可以勝任散列表索引這一角色,它們必須在索引空間 中儘可能分散開來。這意味着在最理想的情況下,越是類似但不相等 的對象,它們散列值的差異應該越大。
- 散列表算法
爲了獲取 my_dict[search_key] 背後的值,Python 首先會調用 hash(search_key) 來計算 search_key 的散列值,把這個值最低的幾位數字看成偏移量,在散列表裏查找表元(具體取幾位,得看 當前散列表的大小)。若找到的表元是空的,則拋出 KeyError 異 常。若不是空的,則表元裏會有一對 found_key:found_value。 這時候 Python 會檢驗 search_key == found_key 是否爲真,如 果它們相等的話,就會返回 found_value。
若是 search_key 和 found_key 不匹配的話,這種狀況稱爲散列 衝突。發生這種狀況是由於,散列表所作的實際上是把隨機的元素映射到只有幾位的數字上,而散列表自己的索引又只依賴於這個數字 的一部分。爲了解決散列衝突,算法會在散列值中另外再取幾位, 而後用特殊的方法處理一下,把新獲得的數字再看成索引來尋找表 元。若此次找到的表元是空的,則一樣拋出 KeyError;若非 空,或者鍵匹配,則返回這個值;或者又發現了散列衝突,則重複 以上的步驟。
圖 3-3 展現了這個算法的示意圖。
圖 3-3:從字典中取值的算法流程圖;給定一個鍵,這個算法要 麼返回一個值,要麼拋出 KeyError 異常
添加新元素和更新現有鍵值的操做幾乎跟上面同樣。只不過對於前者,在發現空表元的時候會放入一個新元素;對於後者,在找到相對應的表元后,原表裏的值對象會被替換成新值。
另外在插入新值時,Python 可能會按照散列表的擁擠程度來決定是 否要從新分配內存爲它擴容。若是增長了散列表的大小,那散列值所佔的位數和用做索引的位數都會隨之增長,這樣作的目的是爲了 減小發生散列衝突的機率。
表面上看,這個算法彷佛很費事,而實際上就算 dict 裏有數百萬 個元素,多數的搜索過程當中並不會有衝突發生,平均下來每次搜索 可能會有一到兩次衝突。在正常狀況下,就算是最不走運的鍵所遇 到的衝突的次數用一隻手也能數過來。
瞭解 dict 的工做原理能讓咱們知道它的所長和所短,以及從它衍 生而來的數據類型的優缺點。下面就來看看 dict 這些特色背後的 緣由。
dict 的實現及其致使的結果
- 鍵必須是可散列的 一個可散列的對象必須知足如下要求。
(1) 支持 hash() 函數,而且經過 __hash__() 方法所獲得的散列值是不變的。
(2) 支持經過 __eq__() 方法來檢測相等性。
(3) 若 a == b 爲真,則 hash(a) == hash(b) 也爲真。全部由用戶自定義的對象默認都是可散列的,由於它們的散列值由 id() 來獲取,並且它們都是不相等的。
若是你實現了一個類的 eq 方法,而且但願它是可 散列的,那麼它必定要有個恰當的 hash 方法,保證在 a == b 爲真的狀況下 hash(a) == hash(b) 也一定爲真。不然 就會破壞恆定的散列表算法,致使由這些對象所組成的字典和 集合徹底失去可靠性,這個後果是很是可怕的。另外一方面,如 果一個含有自定義的 eq 依賴的類處於可變的狀態,那就不要在這個類中實現 hash 方法,由於它的實例是不可散列的。
- 字典在內存上的開銷巨大
因爲字典使用了散列表,而散列表又必須是稀疏的,這致使它在空間上的效率低下。舉例而言,若是你須要存放數量巨大的記錄,那 麼放在由元組或是具名元組構成的列表中會是比較好的選擇;最好
不要根據 JSON 的風格,用由字典組成的列表來存放這些記錄。用元組取代字典就能節省空間的緣由有兩個:其一是避免了散列表所 耗費的空間,其二是無需把記錄中字段的名字在每一個元素裏都存一 遍。
在用戶自定義的類型中,__slots__ 屬性能夠改變實例屬性的存儲 方式,由 dict 變成 tuple,相關細節在 9.8 節會談到。
記住咱們如今討論的是空間優化。若是你手頭有幾百萬個對象,而 你的機器有幾個 GB 的內存,那麼空間的優化工做能夠等到真正需 要的時候再開始計劃,由於優化每每是可維護性的對立面。
3.鍵查詢很快
dict 的實現是典型的空間換時間:字典類型有着巨大的內存開 銷,但它們提供了無視數據量大小的快速訪問——只要字典能被裝在內存裏。
4.鍵的次序取決於添加順序
當往 dict 裏添加新鍵而又發生散列衝突的時候,新鍵可能會被安 排存放到另外一個位置。因而下面這種狀況就會發生:由 dict([key1, value1), (key2, value2)] 和 dict([key2, value2], [key1, value1]) 獲得的兩個字典,在進行比較的時 候,它們是相等的;可是若是在 key1 和 key2 被添加到字典裏的過程當中有衝突發生的話,這兩個鍵出如今字典裏的順序是不同 的。
- 往字典裏添加新鍵可能會改變已有鍵的順序
不管什麼時候往字典裏添加新的鍵,Python 解釋器均可能作出爲字典擴 容的決定。擴容致使的結果就是要新建一個更大的散列表,並把字 典裏已有的元素添加到新表裏。這個過程當中可能會發生新的散列衝 突,致使新散列表中鍵的次序變化。要注意的是,上面提到的這些 變化是否會發生以及如何發生,都依賴於字典背後的具體實現,因 此你不能很自信地說本身知道背後發生了什麼。若是你在迭代一個 字典的全部鍵的過程當中同時對字典進行修改,那麼這個循環頗有可 能會跳過一些鍵——甚至是跳過那些字典中已經有的鍵。
由此可知,不要對字典同時進行迭代和修改。若是想掃描並修改一 個字典,最好分紅兩步來進行:首先對字典迭代,以得出須要添加 的內容,把這些內容放在一個新字典裏;迭代結束以後再對原有字 典進行更新。
這篇博客介紹python3.6之後的字典實現十分詳細
https://zhuanlan.zhihu.com/p/...
Python3.6以後,往字典裏添加新鍵是有序的,不存在改變已有鍵順序的狀況了
同理,對於集合來講,也是異常消耗內存的;集合有以下特色:
1:集合裏的元素必須是可散列的。
2:集合很消耗內存。
3:能夠很高效地判斷元素是否存在於某個集合。
4:元素的次序取決於被添加到集合裏的次序。
5:往集合裏添加元素,可能會改變集合裏已有元素的次序。