流暢的python第三章, 字典和集合
技術標籤:流暢的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 字典中的散列表
dict中散列表的表元(key引用, value引用),會保證1/3是空的,快要達到閾值時就複製到更大的空間中
my_dict[key]中,首先用hash(key)計算雜湊值,然後用最低的幾位數字當作偏移量,直接去找(為空keyError,不為空,且target_key==key,返回)
如果KeyError,再多取幾位,用特殊方法處理一下,當作索引再次尋找
和java的 hash陣列 + 解決衝突(紅黑樹/ B+數 / 順延等) 思路基本一致, 不過是改成了 散列表(hash()) + 解決衝突(迴圈取值)
7.3 dict的實現及其導致的結果
-
鍵必須是可雜湊的
- 支援hash(),並且通過__hash__()得到的雜湊值是不變的
- 支援通過__eq__()方法來檢查相等
- 若 a == b為真, 則hash(a) == hash(b)也為真
看上面的陳述也就明白了, eq()呼叫的是hash
-
前提
- id()是記憶體, 跟is有關 hash是值,跟==有關(可雜湊的,hash不能變)
- 重要的幾個方法: hash(), ==, eq()
- 使用者自定義的兩個類的雜湊值用的是id,__eq__和==用的都是id,也就是hash完全被id取代了,自定義的類所以都是可雜湊的
- __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]
- 所以python走了兩個極端
- 像set,hash跟值相關,但是不能改變,乾脆不讓值改變不就行了,str,int都是如此,只不過set值改變時會報錯,更容易觀察,而str,int改變了之後就不是之前的物件了,是預設進行的
- 像自定義類,乾脆雜湊值直接用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]
- 下面這三句話很重要
- 傳入key尋找在散列表中的位置的時候,用的是hash
- 找到位置判斷是否相等時,用的是== ,而python中 == 呼叫的是__eq__
- 外加hash跟值相關,只不過自定義的類,不重寫__eq__()呼叫的都是id罷了