1. 程式人生 > 其它 >流暢的python第三章, 字典和集合

流暢的python第三章, 字典和集合

技術標籤:流暢的python筆記


目錄

  1. 只有可雜湊的資料型別才能作為這些對映裡的鍵
  2. 字典推導
  3. 常見的對映方法
  4. 給找不到的鍵返回一個預設值
  5. missing
  6. 集合論
  7. 重點, 深度剖析python字典

1. 只有可雜湊的資料型別才能作為這些對映裡的鍵

如果一個物件是可雜湊的,那麼這個物件的生命週期中,它的雜湊值是不變的,而且這個物件需要實現__hash__(),qe()方法,這樣才能比較,原子不可變的資料型別都是可雜湊的(str, bytes, 和 數值型別),

1.1 元組的話只有當一個元組包含的所有元素都是可雜湊的,他才是可雜湊的
tt = (1, 2, (30, 40))
print hash(tt)
tl = (1, 2, [30, 40]) print hash(tl)

list不是可雜湊的,所以組成的元組也不是可雜湊的
在這裡插入圖片描述


2. 字典推導

例子:

DIAL_CODES = [
    (86, 'China'),
    (91, 'Indian'),
    (1, 'United States'),
    (62, 'Indonesia'),
    (55, 'Brazil'),
    (92, 'Pakistan'),
    (880, 'Bangladesh'),
    (234, 'Nigeria'),
    (7, 'Russia'),
    (81, 'Japan'
) ] print DIAL_CODES print {country: code for code, country in DIAL_CODES} print {code: cou

在這裡插入圖片描述


3. 常見的對映方法

3.1 用get(key, default)代替d[k]
test_dict = {1: '1', 2: '2'}
item = test_dict.get(3, None)
print item

在這裡插入圖片描述

3.2 用setdefault處理找不到的鍵

用這個方法是因為如果要更新某個鍵對應的值的時候,不管使用__getitem__還是get都會不自然,而且效率低,直接使用setdefault

他不香嗎?

test_dict = {1: '1', 2: '2'}
print test_dict.setdefault(3, 'hello world')
item = test_dict.get(3, None)
print item

在這裡插入圖片描述


4. 給找不到的鍵返回一個預設值

4.1 使用get(key, default)方法
test_dict = {1: '1', 2: '2'}
x = test_dict.get(3, 123)
print x

在這裡插入圖片描述

4.2 使用defaultdict類處理

初始化的時候給一個預設返回值的型別,如果是str就返回空串,如果是bool,找不到鍵就返回False,如果寫None,就會報錯,有點傻傻的

import collections

s = [(1, '1'), (2, '2')]
test_dict = collections.defaultdict(str)
for k, v in s:
    test_dict[k] = v
print test_dict[1.2] == ''

在這裡插入圖片描述


5. missing

有一說一, 這個好用多了,只需要在找不到值的時候給我一個預設返回值, dict[key] 呼叫的是 __getitem__(), 如果沒有,就呼叫__missing__(), 並且__missing__只由__getitem__()呼叫

class MyDefualtDict(dict):
    def __missing__(self, key):
        return None


x = MyDefualtDict()
x[1] = 1
print x[1]
print x[2]
print 2 in x
print x.get(2, '1')

在這裡插入圖片描述

總結一下dict常用的方法

dict[] 呼叫的是__getitem__(self, key)
get() 呼叫的就是get(self, key, default=None)
key in dict 呼叫的是 __contains__(self, key)
__missing__()找不到時會呼叫,dict[]會直接呼叫,get()在沒有傳入default時會呼叫


6. 集合論

6.1 集合去重
l = ['spam', 'spam', 'eggs', 'spam']
print l
print list(set(l))

在這裡插入圖片描述

6.2 中綴運算子 交集 & , 合集 | , 差集 -
x = [1, 2, 3, 4]
y = [4, 5, 6, 7]
print set(x) | set(y)
print set(x) & set(y)
print set(x) - set(y)

在這裡插入圖片描述

6.3 集合字面量

空集必須寫成set(), {}是空字典, 語法陷阱

x = {1, 2, 3}
y = set()
print x | y
print x & y
print x - y

在這裡插入圖片描述

6.4 集合推導
# unichr(i) 得到字元的名字
from unicodedata import name
x = {unichr(i) for i in range(1, 256) if 'SIGN' in name(unichr(i), '')}
print x

在這裡插入圖片描述

6.5 集合的操作
# 就是一些基本的集合操作
x = {1, 2, 3, 4, 5}
y = {5, 6, 7, 8, 9}
z = [4, 5, 6]
# 1 交集
# 1.1 a & b  =>  a.__and__(b)    =>  a.intersection(iter,...)
# set([5])
print x & y
# set([5])
print x.__and__(y)
# set([4, 5])
print x.intersection(z)
# 1.2 a &= b    =>  a.__iand__(b)   => a.intersection_update(iter,...)
x.intersection_update(z)
print x

# 2 並集
# 2.1 a | b =>  a.__or__(b) =>  a.union(iter,...)
# 2.2 a |= b    =>  a.__ior__(b)    =>  a.update(iter,...)

# 3 差集
# 3.1 a - b =>  a.__sub__(b)    =>  a.difference(iter,...)
# 3.2 a -= b    =>  a.__isub__(b)   =>  a.difference_update(iter,...)


# 屬於 e in a    => a.__contains__(e)
# 子集 a <= b     ->      a.__le__(b)         ->      a.issubset(iter)
# 真子集 a < b     ->      a.__lt__(b)
# 父集 a >= b     ->      a.__ge__(b)     ->      a.issuperset(iter)
# 真父集 a > b    ->       a.__gt__(b)

7. 重點

7.1 dict和set的背後

dict和set查詢的時間幾乎是可以忽略不見的 1000w中找1000個, dict 0.000337s, set的& 0.000314, list的in 97.948 原因就在於散列表, x in list 速度是遠遠不如 x & y的

7.2 字典中的散列表
  1. dict中散列表的表元(key引用, value引用),會保證1/3是空的,快要達到閾值時就複製到更大的空間中
  2. my_dict[key]中,首先用hash(key)計算雜湊值,然後用最低的幾位數字當作偏移量,直接去找(為空keyError,不為空,且target_key==key,返回)
  3. 如果KeyError,再多取幾位,用特殊方法處理一下,當作索引再次尋找

和java的 hash陣列 + 解決衝突(紅黑樹/ B+數 / 順延等) 思路基本一致, 不過是改成了 散列表(hash()) + 解決衝突(迴圈取值)

7.3 dict的實現及其導致的結果
  1. 鍵必須是可雜湊的

    1. 支援hash(),並且通過__hash__()得到的雜湊值是不變的
    2. 支援通過__eq__()方法來檢查相等
    3. 若 a == b為真, 則hash(a) == hash(b)也為真

    看上面的陳述也就明白了, eq()呼叫的是hash

  2. 前提

    1. id()是記憶體, 跟is有關 hash是值,跟==有關(可雜湊的,hash不能變)
    2. 重要的幾個方法: hash(), ==, eq()
    3. 使用者自定義的兩個類的雜湊值用的是id,__eq__和==用的都是id,也就是hash完全被id取代了,自定義的類所以都是可雜湊的
    4. __eq__是dict中判斷 散列表中的key 和 傳進來的key 是不是相同的方法
      可雜湊的

再看下面一段話
支援hash(),並且通過__hash__()得到的雜湊值是不變的,所以 == 相等, hash()也要相等,這是值相等,並且hash是不可變的 也就是說hash必須跟值掛鉤,但是值改變,hash值並不改變 所以set是不可改變的,列表可變但是沒有hash, 自定義的類可雜湊,但是用的全是id,永遠不可能出現 == 和 hash() == hash()的情況
如下例子最能說明

a = (1, 2, 3)
b = (1, 2, 3)
print id(a)
print id(b)
print a == b
print a.__eq__(b)
print hash(a), hash(b)

test_dict = {}

test_dict[a] = '123'
print test_dict[a]
print test_dict[b]

在這裡插入圖片描述

  1. 所以python走了兩個極端
    1. 像set,hash跟值相關,但是不能改變,乾脆不讓值改變不就行了,str,int都是如此,只不過set值改變時會報錯,更容易觀察,而str,int改變了之後就不是之前的物件了,是預設進行的
    2. 像自定義類,乾脆雜湊值直接用id,不用hash,所以你值隨便變動,但是id記憶體地址是不變的,也是滿足可雜湊的

問題就在於 == 判斷時用的是hash
下面4個return都是一下, 相信你會有很多收穫

class TestClass(object):
    def __init__(self, value):
        self._value = value
    def __hash__(self):
        return self._value
    def __eq__(self, other):
        return self._value == other._value
    #    return self.__hash__() == other.__hash__()
    #     return id(self) == id(other)
    #     return True

a = TestClass(1)
b = TestClass(1)
c = TestClass(1)
test_dict[a] = '1'
print a == b == c
print test_dict[a], test_dict[b], test_dict[c]
  1. 下面這三句話很重要
    1. 傳入key尋找在散列表中的位置的時候,用的是hash
    2. 找到位置判斷是否相等時,用的是== ,而python中 == 呼叫的是__eq__
    3. 外加hash跟值相關,只不過自定義的類,不重寫__eq__()呼叫的都是id罷了