1. 程式人生 > >Fluent_Python_Section2數據類型,03-dict-set,字典和集合

Fluent_Python_Section2數據類型,03-dict-set,字典和集合

python實現 default方法 span unicode 操作 出現的次數 name 運行 state

字典和集合

dict和set都基於hash table實現

1. 大綱:

  1. 常見的字典方法
  2. 如何處理查找不到的鍵
  3. 標準庫中dict類型的變種
  4. setfronzenset類型
  5. Hash table的工作原理
  6. Hash table帶來的潛在影響

字典dict

2. 泛映射類型

技術分享圖片

在collections.abc中,有Mapping和MutableMapping,兩個抽象基類,主要作用是作為形式化文檔,定義了映射類型的基本API。

my_dict = {}
#判定數據是不是廣義上的映射類型
#不用type的原因是這個參數可能不是dict
isinstance(my_dict, abc.Mapping)

標準庫裏的映射類型都是基於dict擴展的,因此它們有個共同的限制,只有hashable的數據類型才能作為key(保持鍵唯一),值沒有這個限制。
https://docs.python.org/3/glossary.html#term-hashable
Hashable對象要實現__hash__()和__eq__()方法

一般來說不可變類型都是可hashtable的,但是有特例,dict雖然是不可變類型,但它裏面的元素可能是可變的類型。

tt = (1, 2, (30, 40))
hash(tt)

#Error, dict裏面的list不是可散列的類型
t1 = (1, 2, [30, 40])
hash
(t1) tf = (1, 2, frozenset([30, 40])) hash(tf)

創建字典的不同方式

>>> a = dict(one=1, two=2, three=3)
>>> b = {‘one‘: 1, ‘two‘: 2, ‘three‘: 3}
>>> c = dict(zip([‘one‘, ‘two‘, ‘three‘], [1, 2, 3]))
>>> d = dict([(‘two‘, 2), (‘one‘, 1), (‘three‘, 3)])
>>> e = dict({‘three‘
: 3, ‘one‘: 1, ‘two‘: 2}) >>> a == b == c == d == e True

3. 字典推導式(dict comprehension, dictcomp)

列表推導式用[],元組推導式用(),字典推導式用{}

dictcomp可以從任何以key-value作為元素的可叠代元素中構建出字典

DIAL_CODES = [
    (86, ‘China‘),
    (91, ‘India‘),
    (1, ‘United States‘),
    (62, ‘Indonesia‘),
    (55, ‘Brazil‘),
    (92, ‘Pakistan‘),
    (880, ‘Bangladesh‘),
    (234, ‘Nigeria‘),
    (7, ‘Russia‘),
    (81, ‘Japan‘),
]

#dictcomp, {}
country_code = {country : code for code, country in DIAL_CODES}

print(country_code)

#以大寫打印code < 65的國家名
temp = {code : country.upper() for country, code in country_code.items()}
print(temp)

4. 常見的映射類型方法

中文電子書P137,dict、collections.defaultdict和collections.OrderedDict的常用方法

5. 用setdefault處理找不到的鍵(key)

d[key]找不到時會拋出KeyError異常,可以用d.get(key, default)來代替,給找不到的key一個默認的返回值。但是要更新某個key對應的value時,用__getitem__和get都是不自然,而且效率低。所以d.get並不是處理找不到的key的最好方法

中文電子書P139,用setdefault處理

dict1 = {‘name‘:‘Allen‘, ‘age‘:18}
#setdefault方法如果有key-value就不動,沒有就添加。這個方法有返回值
print(dict1)

dict1.setdefault(‘age‘, 30)
dict1.setdefault(‘xxx‘, 22)
print(dict1)

6. 用defaultdict處理找不到的鍵 未搞懂

7. 用__missing__處理找不到的鍵

不只字典,所有映射類型在處理找不到的鍵的時候,都會牽扯到__missing__方法。
That is, 如果一個類繼承了dict,然後這個繼承類提供了__missing__方法,那麽在__getitem__碰到找不到的鍵的時候,Python就會自動調用它,而不是拋出KeyError異常。
Note:__missing__方法只會被__getitem__調用,例如d[key]。另外對get或contains(in運算符用到這個方法)沒有影響。

像 k in my_dict.keys()在Python3中效率是很高的,因為dict.keys()返回的是dictionary-view-objects。而在Python2中,dict.keys()返回的是一個列表,k in my_list操作需要掃描整個列表。

8. 字典的變種

不同的映射類型:

  1. collections.OrderedDict: 在添加鍵的時候會保持順序。
  2. collections.ChainMap:可以容納數個不同的映射對象,在進行鍵查找操作的時候,這些對象會被當作一個整體被逐個查找。這個特性在給有嵌套作用域的語言做解釋器時非常有用,可以用一個映射對象來代表一個作用域的上下文。
  3. collections.Counter這個映射類型可以給hashable的對象計數,或者是當作多重集來用。Counter實現+和-運算符來合並記錄,還有most_common([n])會按照次序返回映射裏最常見的n個鍵和它們的計數。
from collections import Counter

str1 = ‘aabbccdd‘

counter_str = Counter(str1)
print(type(counter_str))
print(counter_str)

counter_str.update(‘aabbccdd‘)
print(counter_str)
print(counter_str.most_common(3))

4.collections.UserDict,這個類把標準dict用純Python實現了一遍。讓用戶繼承來寫子類的。

9. 自定義映射類型

所有映射類型都是基於dict的。自定義映射類型,以UserDict為基類,比普通的dict為基類方便。
因為如果從dict繼承,dict有時候在某些方法上走一些捷徑,導致子類要重寫這些方法,但UserDict不會有這些問題。具體看中文電子書12.1節。
Note:UserDict並不是dict的子類,是MutableMapping的子類,但是其中有一個叫data的屬性是dict的實例,這個是UserDict存儲數據的地方。好處是UserDict的子類實現__setitem__和__contains__時代碼更簡潔。

例子1. 添加、更新還是查詢操作,StrKeyDict都會把非字符串的鍵轉換為字符串

import collections

class StrKeyDict(collections.UserDict):

    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains(self, key):
        return str(key) in self.data

    def __setitem__(self, key, value):
        self.data[str(key)] = value

StrKeyDict裏剩下的映射類型的方法都是從UserDict、MutableMapping和Mapping這些超類繼承而來。特別是Mapping這個ABC(抽象基類),以文檔化的形式提供了實用的API。以後兩個方法值得註意:

  1. MutableMapping.update。可以直接利用,可以用在__init__裏。原理是self[key] = value來添加新值,所以它是使用__setitem__方法。
  2. Mapping.get。

10. 不可變映射類型

之前有不可變的序列類型,但是在標準庫中沒有不可變的字典類型,但是可以用替身來代替。

字典是可以動態修改的,但不希望用戶修改,所以需要一個只讀的映射視圖。types.MappingProxyType返回一個只讀的映射視圖。

from types import MappingProxyType

d = {‘1‘ : ‘A‘}
d_proxy = MappingProxyType(d)
print(‘d_proxy:‘, d_proxy)
#MappingProxyType只讀,沒有賦值操作
#d_proxy[2] = ‘x‘
d[2] = ‘x‘
#MappingProxyType是動態的,也就是對d所做的任何改動都會反饋到它上面。
print(‘d_proxy:‘, d_proxy)

集合set

11. 集合

set的本質是許多唯一對象的聚集。因此,集合可以去重

l1 = [‘spam‘, ‘spam‘, ‘eggs‘, ‘eggs‘]
s1 = set(l1)
print(s1)
print(list(s1))

set中的元素必須是hashable(保證唯一),但set本身是不可散列的,但是frozenset是hashable的。因此可以創建一個包含不同frozenset的set。set裏面的元素是hashable的,所以搜索速度極快。

set還有很多基礎的中綴運算符,a | b並集,a & b交集,a - b差集。可以省去不必要的循環和邏輯操作。

例子1. needles元素在haystack裏出現的次數,兩個變量都是set類型

found = len(needles & haystack)

例子2. needles和haystack是任何兩個可叠代對象

found = 0
for n in needles:
    if n in haystack:
        found += 1

例子3. 轉換為set再運算

found = len(set(needles) & set(haystack))

#另一種寫法
found = len(set(needles).intersection(haystack))

12. 集合字面量

{1}, {1, 2}是集合的字面量,set()是空集, {}是空字典。

字面量{1,2,3}比構造方法(set([1,2,3]))更快。集合字面量,Python會利用BUILD_SET的字節碼來創建集合。

from dis import dis
dis({1})

dis(‘set([1])‘)

創建frozenset只能用構造方法

frozenset(range(10))

13. 集合推導式(set comprehension, setcomps)

列表推導式用[], 元組推導式用(), 字典和集合推導式用{}

例子1. 用setcomps創建一個Latin-1字符集合

#獲取字符的名字name
from unicodedata import name
#把編碼32~255之間的字符的名字裏有"SIGN"單詞挑出來,放到一個集合裏面
s1 = {chr(i) for i in range(32, 256) if ‘SIGN‘ in name(chr(i),‘‘)}

print(s1)
print(name(‘+‘,‘‘))

14. 集合的操作

技術分享圖片

collections.abc(抽象基類),提供API信息。

集合的數學運算: 中文電子書P161
集合的比較運算: 中文電子書P162
集合的其他方法: 中文電子書P163

15. dict和set的原理

dict和set都是借助hash table來實現功能的。例如in運算,所以速度快。
Note:列表的背後沒有用散列表來支持in運算符,每次搜索都是順序遍歷。

16. 字典中的散列表

如果對象A == 對象B,那麽hash(A) == hash(B)。調用hash(),實際上運行的是__hash__。

dict取值原理采用散列表算法,中文電子書P169

17. 使用散列表實現dict帶來的優勢和限制

中文電子書P171

  1. key必須是hashable的
  2. 字典在內存上的開銷巨大,因為hash table是稀疏數組
  3. key查詢很快,因為hash table是classic的空間換時間
  4. key的順序取決於添加順序
  5. 往字典裏添加新鍵可能會改變已有鍵的順序,不要對字典同時進行叠代和修改。因為Python解釋器可能做出為字典擴容的決定,把舊表復制到一個新的更大的散列表時,可能發生散列沖突。
    6. .keys()、.items()、.values()返回的是字典的視圖,動態反饋字典的變化。

18. 使用散列表實現set帶來的優勢和限制

set和frozenset的實現也依賴散列表,但在它們的hash table存放的只有元素的引用(就像在字典裏只存key而沒有相應的值)。
Note:在set加入Python前之前,我們都是把字典加上無意義的value當集合使用。

和散列表實現dict的優點和缺點類似:

  1. 集合裏的元素必須是可散列的。
  2. 集合很消耗內存。
  3. 可以很高效地判斷元素是否存在於某個集合中。
  4. 元素的次序取決於被添加到集合裏的次序。
  5. 往集合裏添加元素,可能會改變集合裏已有元素的次序。

Fluent_Python_Section2數據類型,03-dict-set,字典和集合