本文主要內容 可散列類型 泛映射類型 字典 (1)字典推導式 (2)處理不存在的鍵 集合 映射的再討論 python高級——目錄 文中代碼均放在github上:https://github.com/ampeeg/cnblogs/tree/master/python高級 可散列類型 泛映射類型 字典 ...
本文主要內容
可散列類型
泛映射類型
字典
(1)字典推導式
(2)處理不存在的鍵
(3)字典的變種
集合
映射的再討論
文中代碼均放在github上:https://github.com/ampeeg/cnblogs/tree/master/python高級
可散列類型
''' 可散列數據類型(也稱可hash)————我理解"可散列"就是"可hash" 可hash的對象需要實現__hash__方法,返回hash值;另外為了與其他對象比較還需要有__eq__方法 原子不可變數據類型(str、bytes和數值類型)都是可散列的,可散列對象必須滿足下列要求: (1)實現了__hash__方法,並且所得到的hash值是不變的 (2)實現了__eq__方法,用來比較 (3)若a == b 為真,那麼hash(a) == hash(b)也是真 ''' # 創建類Foo,並實現__hash__和__eq__ class Foo: def __init__(self, name): self.name = name def __hash__(self): print("正在hash...") return hash(self.name) def __eq__(self, other): print("正在比較...") return self.name == other.name def __repr__(self): return self.name if __name__ == "__main__": f1 = Foo("小李") f2 = Foo("小紅") f3 = Foo("小李") s = set([f1, f2, f3]) # 集合實現不重覆的原理正好利用了散列表 print(s) # {小紅, 小李} print( f1 == f3, hash(f1) == hash(f3)) # True True 滿足可散列對象的第三個條件 |
''' 對於元組來說,只有當一個元組包含的所有元素都是可hash的情況下,它才是可hash的 ''' t1 = (1, 2, 3, [1, 2]) # 元組裡的列表的值是可變的,所以不可hash try: print(hash(t1)) except Exception as e: print(e) # unhashable type: 'list' t2 = (1, 2, 3, (1, 2)) # 元組裡的元素都是不可變的,並且第二層元組裡面的元素也不可變,所以可hash print(hash(t2)) # 3896079550788208169 t3 = (1, 2, 3, frozenset([1, 2])) print(hash(t3)) # -5691000848003037416 |
泛映射類型
''' 泛映射類型就是廣義上的對應關係,在數學中,我們將集合A對應集合B中的對應法則稱為"映射"(Mapping) 同樣,在python里,我們稱"鍵值對"為映射,這其實也是一種對應法則 如果一個數據類型是映射,那麼它肯定屬於collections.abc.Mapping,可使用isinstance函數測試 PS: 字典是 Python 語言中唯一的映射類型。映射類型對象里哈希值(鍵) 和指向的對象(值)是一對多的關係。 ''' from collections import abc # 我們測試一些常用的類型是不是映射 if __name__ == "__main__": print(isinstance({}, abc.Mapping)) # True 字典是典型的鍵值對 print(isinstance([1, 2], abc.Mapping)) # False 列表是序列 print(isinstance((1, 2), abc.Mapping)) # False 元組是序列 print(isinstance('adfasfd', abc.Mapping)) # False 字元串也是序列 |
''' 大家可以查看_collections_abc.py源代碼,裡面基本的類型包含: ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", "Hashable", "Iterable", "Iterator", "Generator", "Sized", "Container", "Callable", "Set", "MutableSet", "Mapping", "MutableMapping", "MappingView", "KeysView", "ItemsView", "ValuesView", "Sequence", "MutableSequence", "ByteString", ] ''' |
''' 如果我們自己想定義一個映射類型的對象,那麼必須實現__getitem__、__iter__、__len__方法 PS:關於該部分的原理,本人暫未查看說明文檔,畢竟現實中幾乎不可能自定義映射;有興趣的同志可深入鑽研。 ''' class Foo(abc.Mapping): def __init__(self, name): self.name = name def __getitem__(self, item): return self.name def __iter__(self): return iter(str(self.name)) def __len__(self): return len(self.name) print(isinstance(Foo("123"), abc.Mapping)) # True |
字典
''' 字典是python內置類型中唯一的映射,先看創建字典的幾種方法 1、對象創建 2、大括弧 3、zip ''' if __name__ == "__main__": # 1、利用實例化對象的方法創建 a = dict(key1=1, key2=2, all=[1, 2, 3]) b = dict([('key3', 3), ('key4', 4)]) c = dict({"key5": 5, "key6": 6}) print("a:", a) # a: {'key1': 1, 'all': [1, 2, 3], 'key2': 2} print("b:", b) # b: {'key3': 3, 'key4': 4} print("c:", c) # c: {'key6': 6, 'key5': 5} # 2、直接使用大括弧 d = {"key7": 7, "key8": 8} print("d:", d) # d: {'key8': 8, 'key7': 7} # 3、使用zip e = dict(zip(("key9", "key10", "key11"), [9, 10, 11])) print("e:", e) # e: {'key11': 11, 'key10': 10, 'key9': 9} |
(1)字典推導式
''' 字典推導式:字典推導式的創建方法同列表推導式類似 以下直接引用《流暢的python》中的例子 ''' if __name__ == "__main__": DIAL_CODES = [ (86, 'China'), (91, 'India'), (1, 'United States'), (62, 'Indonesia'), (55, 'Brazil'), (92, 'Pakistan'), (880, 'Bangladesh'), (234, 'Nigeria'), (7, 'Russia'), (81, 'Japan'), ] country_code = {country: code for code, country in DIAL_CODES} print(country_code) # {'Russia': 7, 'Indonesia': 62, 'Brazil': 55, 'China': 86, 'India': 91, 'Bangladesh': 880, 'Pakistan': 92, 'United States': 1, 'Nigeria': 234, 'Japan': 81} code_upper = {code: country.upper() for country, code in country_code.items() if code < 66} print(code_upper) # {1: 'UNITED STATES', 7: 'RUSSIA', 62: 'INDONESIA', 55: 'BRAZIL'} |
(2)處理不存在的鍵
''' 處理找不到的鍵 在實際場景中,當使用d[key]的方法查找數據的時候,如果找不到該鍵,python會拋出KeyError異常; 如果是取值操作,可以使用d.get(key, default)來解決,可以給找不到的鍵一個預設的值 但是如果要給更新某個不存在鍵對應的值的時候,就稍顯麻煩了,可以使用以下方法解決: 1、用setdefault處理dict找不到的鍵 2、使用defaultdict對象 3、__missing__方法 ''' class Foo: def __init__(self, name=None): self.name = name def __repr__(self): return str(self.name) def setattr(self, key, value): self.__setattr__(key, value) return self if __name__ == "__main__": d1 = {} print(d1.get("key", "default")) # default 使用d.get(key, default)的方法取值 # 1、用setdefault處理dict找不到的鍵 d2 = {} d2.setdefault("key", [x for x in "adfaf"]) # setdefault雖然是set名字,但是是取值操作,只有當鍵不存在時才進行賦值,並返回該值 l = d2.setdefault("key", []) print(l) # ['a', 'd', 'f', 'a', 'f'] d2.setdefault("key2", []).extend([1, 2, 3]) # 返回空列表,所以可在後面直接使用方法extend print(d2) # {'key': 'default', 'key2': [1, 2, 3]} # 2、使用defaultdict對象 # 在python中,還有一些dict的變種類型,defaultdict為其中一種,位於collections中 from collections import defaultdict dic = defaultdict(list) # 將list的構造方法作為default_factory(只有__getitem__找不到值時調用) dic["key"].extend([1, 2, 3]) # dic中不含有"key"鍵,此時default_factory會被調用,創造一個空列表,並連接[1, 2, 3] print(dic["key"]) # [1, 2, 3] dic = defaultdict(Foo) # 將Foo的構造方法作為default_factory創建一個defaultdict print(dic["key"].setattr("name", "default")) # default # 3、__missing__方法 # 所有的映射類型在找不到鍵的時候,都會牽扯到__missing__方法;如果在__getitem__找不到鍵的時候,python就會自動調用它 # 另外,__missing__方法只會被getitem調用,對get或者__contains__沒有影響 class My_dict(dict): def __missing__(self, key): print("正在調用__missing__...") mdict = My_dict(one=1, two=2, three=3) print(mdict) # {'two': 2, 'three': 3, 'one': 1} mdict["key"] # 正在調用__missing__... |
(3)字典的變種
''' 在python中雖然只有dict為映射類型,但是dict有很多變種,上面defaultdict就是,除此之外還有: (1)OrderedDict: 有順序的字典 (2) ChainMap: 可以容納數個不同的映射對象 (3) Counter: 給鍵準備一個整數計數器,每次更新鍵的時候會增加該計數器 (4)UserDict: 將標準的dict用python實現了一遍 ''' from collections import OrderedDict, ChainMap, Counter, UserDict if __name__ == "__main__": # 1、OrderedDict d = OrderedDict() d['one'] = 1 d['two'] = 2 d['three'] = 3 for _ in range(10): print("%d次:" % _) for k, v in d.items(): print("**", k, v) # OrderedDict迭代的時候的順序總是跟插入順序一致 # 2、ChainMap pylookup = ChainMap(d, globals()) # d和globals()都是映射類型,ChainMap會將其組合 for v, k in pylookup.items(): print(v, k) # 3、Counter ct = Counter('asfjlajslfjals') print(ct) # Counter({'j': 3, 'l': 3, 's': 3, 'a': 3, 'f': 2}) # 存儲的是每個字母出現的次數 ct.update('jjjjjjjjlllllllll') print(ct) # # Counter({'l': 12, 'j': 11, 's': 3, 'a': 3, 'f': 2}) import random ct2 = Counter([random.randrange(1, 5) for _ in range(100)]) # 列表推導式創建Counter print(ct2) # Counter({1: 30, 2: 24, 4: 24, 3: 22}) ct3 = Counter((random.randrange(1, 5) for _ in range(100))) # 生成器創建Counter print(ct3) # Counter({2: 40, 3: 23, 4: 20, 1: 17}) class Foo: def __init__(self, num): self.l = [random.randrange(1, 5) for _ in range(num)] def __iter__(self): return iter(self.l) ct4 = Counter(Foo(100)) # 可迭代對象創建Counter print(ct4) # Counter({2: 31, 3: 25, 4: 25, 1: 19}) # 4、UserDict # 創建自定義的映射類型,一般以UserDict為基類 class My_dict(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, item): print("調用__setitem__。。。") self.data[str(key)] = item mdict = My_dict() mdict["one"] = 1 # 調用__setitem__。。。(下同) mdict["two"] = 2 mdict["three"] = 3 print(mdict) # {'three': 3, 'one': 1, 'two': 2} |
集合
''' 集合對於很多人並不陌生,中學階段就已經接觸過。集合具有: (1)確定性:每一個對象都能確定是不是某一集合的元素,沒有確定性就不能成為集合 (2)互異性:集合中任意兩個元素都是不同的對象 (3)無序性:{a,b,c}{c,b,a}是同一個集合 在python中,set中的元素必須是可散列的,但set本身不可散列(但是frosenset是可散列的) 另外:set實現了很多基礎運算 &(交集)、|(並集)、-(差集) ''' if __name__ == "__main__": # 創建集合 s1 = set([1, 2, 3]) s2 = {1, 2, 3, 4} print(s1, s2) # {1, 2, 3} {1, 2, 3, 4} # 集合推導式 s3 = {x**2 for x in range(10)} print(s3) # {0, 1, 64, 4, 36, 9, 16, 49, 81, 25} |
set的操作方法很多,本文截自<流暢的python>一書,如下三個表:
表一:集合的數學方法
表2:集合的比較運算
表3:集合的其他運算
映射的再討論
''' python標準庫裡面的映射類型都是可變的,有時候需要使用不可變的映射,從python3.3開始,types模塊中引入了 MappingProxyType類,如果給這個類一個映射,那麼它會返回這個映射的試圖,該試圖是動態的,原映射如果有改動 可立即通過這個試圖觀察到,但是這個試圖無法對該映射進行修改。 ''' from types import MappingProxyType if __name__ == "__main__": d = {'one':1, 'two':2, 'three':3} d_proxy = MappingProxyType(d) print(d_proxy) # {'three': 3, 'two': 2, 'one': 1} print(d_proxy['one']) # 1 for k, v in d_proxy.items(): print(k, v) #d_proxy['four'] = 4 # 報錯:TypeError: 'mappingproxy' object does not support item assignment d['four'] = 4 print(d_proxy) # {'two': 2, 'three': 3, 'four': 4, 'one': 1} |
另外,《流暢的python》77頁到80頁對散列表演算法以及字典、集合的效率、平時需要註意的問題進行了比較詳細的探討,建議嚴謹並有興趣的同仁閱讀,該部分內容對理解字典類型無比有益,場景中捉摸不透的莫名其妙的bug可能會迎刃而解。
重要的結論摘錄如下:
(1)鍵必須是可散列的
(2)字典在記憶體上的開銷巨大
(3)鍵查詢很快
(4)鍵的次序取決於添加順序
(5)往字典里添加新鍵可能會改變已有鍵的順序
python高級系列文章目錄