概覽
????在collections.abc中定義了Mapping和MutableMapping去定義dict和類似類型的接口点额。一般的特殊類型的映射(mappings)都是繼承自dict或者collections.UserDict著蛙。
Hashable
????所有在標(biāo)準(zhǔn)庫里的映射類型都是繼承自dict,所以他們都有一個限制,key必須是hashable的均牢。
????一個對象是hashable的有三點:
1.它有一個hash值在整個生命周期內(nèi)都是不變的(需要一個__hash__()
方法)。
2.能夠和其他對象比較(需要一個__eq__()
方法)
3.hashable的對象如果比較是相等的必須有相同的hash值。
????常見的hashable的類型:
1.原子的不可變類型(immutable)都是hashable的亿汞,比如str, bytes, numeric類型。
2.frozenset總是hashable的揪阿,因為它的元素要求必須都是hashable的疗我。
3.tuple如果所有的元素都是hashable的就是hashable的咆畏。
4.用戶自定義類型缺省是hashable的,因為他們的hash值就是他們的id()并且是不相等的吴裤。如果一個對象實現(xiàn)了自定義__eq__
方法旧找,就需要考慮到這個對象的內(nèi)部狀態(tài),只有當(dāng)所有的屬性是immutable的這個對象才是hashable的麦牺。
>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> class A:
... def __init__(self):
... pass
...
>>> a = A()
>>> b = A()
>>> hash(a)
-9223372036571095159
>>> hash(b)
283680646
>>> a == b
False
????關(guān)于__hash__()
和__eq__
的自定義這里還有些特殊的要求钮蛛,詳細(xì)可以看python的手冊中相關(guān)的內(nèi)容。
????這里還引申出另一個問題就是is
和==
的區(qū)別剖膳。is
比較的是對象的id
是否相同魏颓,意義在于是否是同一個實例對象,是否是同一個內(nèi)存地址潮秘。而==
比較的是兩個對象的內(nèi)容是否相同琼开,默認(rèn)會調(diào)用對象的__eq__
方法。繼承自 object 的__eq__
方法比較兩個對象的id枕荞,結(jié)果與 is 一樣柜候。但是多數(shù)Python的對象會覆蓋object的__eq__
方法,而定義內(nèi)容的相關(guān)比較躏精,所以比較的是對象屬性的值渣刷。
>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b == a
True
>>> b = a[:]
>>> b is a
False
>>> b == a
True
????數(shù)字類型用is
比較有一些特殊,對于[-5, 256]區(qū)間內(nèi)的small_ints矗烛,python會進(jìn)行緩存辅柴,不會創(chuàng)建新的對象。
>>> a = 256
>>> b = 256
>>> a is b
True
>>> a == b
True
>>> a = 257
>>> b = 257
>>> a is b
False
>>> a == b
True
????字符串使用is
的結(jié)果也不一定是相同瞭吃。
>>> c = 'abc.com'
>>> b = 'abc.com'
>>> c is b
False
>>> c == b
True
>>> c = 'efg'
>>> d = 'efg'
>>> c is d
True
>>> c == d
True
????tuple碌嘀,list,set的is
和==
的結(jié)果都不一樣歪架。
>>> a = (1, 2, 3)
>>> b = (1, 2, 3)
>>> a == b
True
>>> a is b
False
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True
>>> a is b
False
>>> a = set([1, 2, 3])
>>> b = set([1, 2, 3])
>>> a == b
True
>>> a is b
False
dict Comprehensions
????創(chuàng)建dict有如下幾種方式股冗。
>>> 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
>>> DIAL_CODES = [
... (86, 'China'),
... (91, 'India'),
... (81, 'Japan'),
... ]
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'Japan': 81}
處理缺失值
????同一個邏輯的幾種實現(xiàn)方式。
>>> my_dict = {}
>>> key = 'a'
>>> if key not in my_dict:
... my_dict[key] = []
...
>>> my_dict[key].append("value")
>>> my_dict
{'a': ['value']}
>>> my_dict = {}
>>> key = 'a'
>>> my_list = my_dict.get(key, [])
>>> my_list.append("value")
>>> my_dict[key] = my_list
>>> my_dict
{'a': ['value']}
>>> my_dict = {}
>>> key = 'a'
>>> my_list = my_dict.setdefault(key, [])
>>> my_list.append("value")
>>> my_dict
{'a': ['value']}
>>> my_dict = collections.defaultdict(list)
>>> key = 'a'
>>> my_dict[key].append("value")
>>> my_dict
defaultdict(<class 'list'>, {'a': ['value']})
>>> class ListDict(dict):
... def __missing__(self, key):
... return []
...
>>> a = ListDict()
>>> my_list = a[key]
>>> my_list
[]
>>> my_list.append("value")
>>> a[key] = my_list
>>> a
{'a': ['value']}
????對于前三種方法和蚪,直接使用dict止状,setdefault會比前兩種方法更高效。如果需要更靈活的對缺失值的處理攒霹,可以采用第四種方法(default_dict)和第五種方法(繼承dict)怯疤。
????缺失值處理的實質(zhì)就是__missing__
這個特殊方法的調(diào)用,這個方法只會在__getitem__
(就是像d[k]這種形式)碰到缺失值時才會調(diào)用催束,而對get
集峦,__contains__
(in方法)等其他dict的方法沒有影響。所以在處理缺失值的時候,其他的方法也需要相應(yīng)的處理少梁。
其他類型的dict
collections.OrderedDict:按照key的插入順序維護(hù)keys洛口,可以以一個可預(yù)測的順序?qū)ey進(jìn)行遍歷。還可以popitem第一個或者最一個元素凯沪。
collections.ChainMap:把幾個dict放在一起進(jìn)行搜索第焰,搜索按照dict的順序進(jìn)行,在任意一個dict中找到key就會搜索成功妨马。比如在變量查找時挺举,可以先查找本地變量,再查找全局變量烘跺,最后查找系統(tǒng)變量湘纵。還有對命令行參數(shù),可以先查找用戶輸入的變量滤淳,再查找變量的默認(rèn)值梧喷。
collections.Counter:對每個key維護(hù)一個計數(shù),可以通過update
方法更新一個key增加計數(shù)脖咐,還有most_common
返回出行次數(shù)最多的n個key铺敌。
Subclassing UserDict
????一般創(chuàng)建一個新的mapping類型通過繼承UserDict
會比繼承dict
要容易。主要原因在于built-in對象的一些實現(xiàn)會走捷徑屁擅,導(dǎo)致你必須去重載方法偿凭,而繼承UserDict
不會有這些問題。具體的原因還會在后續(xù)的章節(jié)詳細(xì)講解派歌。
Immutable Mappings
????MappingProxyType
提供了一個原始mapping的動態(tài)識圖弯囊,意味著原始的mapping的更新能夠在這個mappingproxy中看到,但是不能通過他對原始的mapping進(jìn)行更新胶果。
>>> from types import MappingProxyType
>>> d={1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'
Set
????set是一個唯一對象的集合匾嘱,可以用于去重。set存儲的元素必須是hashable的早抠,但是set本身不是hashable的奄毡,但frozenset是hashable的。
>>> l = ['spam', 'spam', 'eggs', 'spam']
>>> set(l)
{'spam', 'eggs'}
>>> hash(set(l))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
????set提供一些中綴操作符贝或,如a | b
表示union,a & b
表示intersection锐秦,a - b
表示difference咪奖。
????創(chuàng)建set使用{},但是要注意一個空的集合需要使用set()
來創(chuàng)建酱床,否則創(chuàng)建出來的是一個dict羊赵。
>>> s={1}
>>> type(s)
<class 'set'>
>>> s={}
>>> type(s)
<class 'dict'>
>>> s = {i for i in range(10)}
>>> s
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> type(s)
<class 'set'>
dict and set Under the Hood
????核心就是hash table,也是為什么dict和set的元素都需要是hashable的。不過和java相比昧捷,python解決hash沖突使用的是開放尋址法闲昭,而java使用的是鏈接法。使用開放尋址法導(dǎo)致內(nèi)存開銷較大靡挥,一旦無法解決沖突序矩,就需要擴充內(nèi)存,并且進(jìn)行元素的重新分布跋破。不過適合于key的快速查找簸淀。