python高級(三)—— 字典和集合(泛映射類型)

本文主要內容

可散列類型html

泛映射類型python

字典git

    (1)字典推導式github

  (2)處理不存在的鍵算法

    (3)字典的變種app

集合dom

映射的再討論函數

 

python高級——目錄post

文中代碼均放在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內置類型中惟一的映射,先看建立字典的幾種方法

    一、對象建立
    二、大括號
    三、zip
'''

if __name__ == "__main__":
    # 一、利用實例化對象的方法建立
    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}

    # 二、直接使用大括號
    d = {"key7": 7, "key8": 8}
    print("d:", d)     # d: {'key8': 8, 'key7': 7}

    # 三、使用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)來解決,能夠給找不到的鍵一個默認的值
    可是若是要給更新某個不存在鍵對應的值的時候,就稍顯麻煩了,可使用如下方法解決:
        一、用setdefault處理dict找不到的鍵
        二、使用defaultdict對象
        三、__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)的方法取值


    # 一、用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]}

    # 二、使用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

    # 三、__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__":
    # 一、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迭代的時候的順序老是跟插入順序一致


    # 二、ChainMap

    pylookup = ChainMap(d, globals())   # d和globals()都是映射類型,ChainMap會將其組合
    for v, k in pylookup.items():
        print(v, k)

    # 三、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})

    # 四、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高級系列文章目錄

python高級——目錄

相關文章
相關標籤/搜索