主要內容源自解讀《Fluent Python》,理解如有錯誤敬請指正:-)
- dict對象的最原始的接口描述是 collections 模塊中的 Mapping 和 MutableMapping 這兩個虛擬類,如下所示:
但是自定義的mapping類卻大多繼承 dict 或者 collections.UserDict 類來實現。不過通??梢允褂?
isinstance(mapObj, collections.Mapping)
而不是 isinstance(mapObj, dict)
這樣的方式來更廣義地判斷一個對象是不是一個符合標準map接口的對象
- Python官方文檔中對于一個對象是否可哈希(Hashable),定義為:
An object is hashable if it has a hash value which never changes during its lifetime (it needs a hash()
method), and can be compared to other objects (it needs an eq()
or cmp()
method). Hashable objects which compare equal must have the same hash value.
核心就是該對象必須要實現 __hash()__ 方法 和 __eq__() 方法,前者就是計算該對象的hash值,是內建函數 hash(obj) 的實際工作方法,可哈希的對象的哈希值必須是恒久不變的,并且__eq__()判斷為相等的兩個對象的哈希值也必須相同。
Python中內建的Hashable對象包括:str、bytes、數字類型、frozenset等immutable對象,所有的可變序列對象都不是可哈希的,對于tuple則僅當其中所有的元素都是Hashable對象時才是可哈希的。
用戶自定義的對象通常是可哈希的,這是因為這些對象的 __hash__() 方法默認返回的是該對象的id(即id(obj)返回的值),而一個對象的id是唯一的
>>> hash((1,2,3,4))
485696759010151909
>>> hash((1,2,[3,4]))
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
hash((1,2,[3,4]))
TypeError: unhashable type: 'list'
>>> hash((1,2,frozenset([3, 4])))
-4138728974339688815
>>> class MyObj:
pass
>>> hash(MyObj())
273870000
>>> hash(MyObj())
273869928
—— Python標準庫中的所有mapping類型對象,都滿足其所有的key必須為可哈希對象的條件,這樣也才能夠保證mapping對象中key的唯一性
- 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)]) # 元素類型為(key, value)的list、tuple等iterable對象都可以直接進行dict化
>>> e = dict({'three': 3, 'one': 1, 'two': 2})
>>> a == b == c == d == e
True
除此以外,還可以使用類似于 listcomps 的推導表示方法,基于元素類型為(key, value)的list、tuple等iterable對象,外層使用 大括號對{}來推導生成dict對象,即所謂的“dict comprehensions”
>>> areaCodeList = [
("Beijing", "010"), ("Guangzhou", "020"), ("Shanghai", "021"), ("Tianjin", "022"), ("Chongqing","023"),
("Shenyang", "024"), ("Nanjing", "025"), ("Wuhan", "027"), ("Chengdu", "028"), ("Xian", "029")]
>>> { code : city for city,code in areaCodeList}
{'025': 'Nanjing', '010': 'Beijing', '024': 'Shenyang', '027': 'Wuhan', '021': 'Shanghai', '020': 'Guangzhou',
'023': 'Chongqing', '022': 'Tianjin', '029': 'Xian', '028': 'Chengdu'}
>>> { code : city for city,code in areaCodeList if code.startswith("02")}
{'025': 'Nanjing', '024': 'Shenyang', '027': 'Wuhan', '021': 'Shanghai', '020': 'Guangzhou', '023': 'Chongqing',
'022': 'Tianjin', '029': 'Xian', '028': 'Chengdu'}
-
dict、defaultdict、OrderedDict三種常用的mapping對象的主要方法和比較如下所示:
所有的mapping類型對象都可以通過 update( ) 函數,傳入新的mapping,或(key, value)作為元素的iterable對象,或者**kwargs參數進行內容的升級:
>>> dic1 = {"Beijing":"error"}; dic1
{'Beijing': 'error'}
>>> dic1.update(areaCodeList, Shenzhen="0755", Hangzhou="0571"); dic1
{'Beijing': '010', 'Shanghai': '021', 'Chengdu': '028', 'Tianjin': '022', 'Guangzhou': '020', 'Xian': '029', 'Nanjing': '025',
'Wuhan': '027', 'Hangzhou': '0571', 'Shenzhen': '0755', 'Shenyang': '024', 'Chongqing': '023'}
- mapping對象的 mapObj.setdefault(key, [default]) 是一個值得特別注意的方法,我們不要被它的名字所迷惑而以為它是一個純粹設置mapping對象中元素默認值的方法,它首先是一個讀取指定key的value的方法,將會返回mapObj中的一個key的value,只是這個value將根據傳入的key是否在當前的mapObj中而有所不同,如果不存在key將會在mapObj中加入 key:default 的元素,并返回default;如果key存在,則直接返回當前key對應的value值,而不會去修改當前key的值為default。
>>> dic2 = {}
>>> dic2.setdefault("key1"); dic2
{'key1': None}
>>> dic2.setdefault("key2",[])
[]
>>> dic2
{'key2': [], 'key1': None}
>>> dic2.setdefault("key3","hello")
'hello'
>>> dic2
{'key3': 'hello', 'key2': [], 'key1': None}
>>> dic2.setdefault("key2")
[]
>>> dic2.setdefault("key3", "value3") # 這一步并不會將key3對應的value進行修改
'hello'
>>> dic2
{'key3': 'hello', 'key2': [], 'key1': None}
>>>
使用setdefault( )方法的優點主要是:
(1)、這個方法的調用不會出現因為key不存在而拋異常的情況,
(2)、對于需要先判斷mapObj中是否包含某個key,然后再進行下一步操作的邏輯,使用這個方法將會使代碼邏輯很簡單,并且至少減少一次為了確認mapObj是否包含key而對整個mapObj對象key的遍歷。
- mapping對象中對于 mapObj[key] 這個語法的支持本質上是調用 mapObj.__getitem__(key) 方法,但是當 __getitem__(key) 方法無法找到key的時候,mapObj對象的 __missing__(key) 方法就會被調用。
(1). 對于dict對象,__missing__(key) 默認會拋出 KeyError 異常;
(2). 對于 collections.defaultdict 對象,__missing__(key) 默認將會調用mapObj對象的default_factory屬性,由default_factory來生成一個對象作為key的value——這一點功能類似于 setdefault( ) 方法
特別需要注意的一點是,__missing__( ) 方法的調用僅僅會由 mapObj[key] 這個語法來觸發,而 mapObj.get(key, default) 這個方法的調用是不會觸發__missing__方法的
>>> class MyDict(dict):
def __missing__(self, k):
return "MissingValue of %s" % str(k)
>>> my_dict = MyDict()
>>> my_dict["key1"]
'MissingValue of key1'
>>> my_dict
{}
>>> my_dict.get("key1") # get(k[, default]) 不會觸發 __missing__( ) 方法被調用
>>> my_dict.get("key1", 3)
3
- collections.defaultdict 對象作用類似于 mapping 對象通用的 setdefault(key, default) 方法,可以避免因為key不存在而導致mapObj[key] 語法觸發KeyError異常。二者的區別在于defaultdict對象是在初始化的時候指定default值產生的方式,而setdefault( ) 方法則是在被調用的時候來動態決定default值。
defaultdict對象初始化時需要傳入default_factory參數,這個參數是一個能夠被調用的屬性,既可以是一個有返回值的函數,也可以是一個類(被調用時即調用該類的初始化方法來返回一個對象)
>>> import collections, time
>>> defdict1 = collections.defaultdict(list)
>>> defdict1
defaultdict(<type 'list'>, {})
>>> defdict1['key1']
[]
>>> defdict1
defaultdict(<type 'list'>, {'key1': []})
>>> def func():
return "CurMilli: %d" % int(time.time() * 1000)
>>> defdict2 = collections.defaultdict(func)
>>> defdict2['key2']
'CurMilli: 1474045511728'
>>> defdict2
defaultdict(<function func at 0x105391ed8>, {'key2': 'CurMilli: 1474045511728'})
- 當構造一個dict的時候,其中的 key:value 對并不是按照插入dict對象時的順序來的。例如:
>>> areaCodeList
[('Beijing', '010'), ('Guangzhou', '020'), ('Shanghai', '021'), ('Tianjin', '022'), ('Chongqing', '023'), ('Shenyang', '024'),
('Nanjing', '025'), ('Wuhan', '027'), ('Chengdu', '028'), ('Xian', '029')]
>>> areaDict = {code : city for city, code in areaCodeList}
>>> areaDict # 其中的item的順序和areaCodeList中的順序并不一致
{'025': 'Nanjing', '010': 'Beijing', '024': 'Shenyang', '027': 'Wuhan', '021': 'Shanghai', '020': 'Guangzhou',
'023': 'Chongqing', '022': 'Tianjin', '029': 'Xian', '028': 'Chengdu'}
如果需要保持這些 key:value 對插入的順序,則需要使用 collections.OrderedDict 類:
>>> areaCodeList
[('Beijing', '010'), ('Guangzhou', '020'), ('Shanghai', '021'), ('Tianjin', '022'), ('Chongqing', '023'), ('Shenyang', '024'),
('Nanjing', '025'), ('Wuhan', '027'), ('Chengdu', '028'), ('Xian', '029')]
>>> sortedAreaDict = collections.OrderedDict([(code, city) for city, code in areaCodeList])
>>> sortedAreaDict
OrderedDict([('010', 'Beijing'), ('020', 'Guangzhou'), ('021', 'Shanghai'), ('022', 'Tianjin'),
('023', 'Chongqing'), ('024', 'Shenyang'), ('025', 'Nanjing'), ('027', 'Wuhan'), ('028', 'Chengdu'), ('029', 'Xian')])
>>> sortedAreaDict.popitem()
('029', 'Xian')
>>> sortedAreaDict.popitem(last=False)
('010', 'Beijing')
- collections.Counter 是一個很有意思的類,它是一個可用于統計Hashable對象數量的dict子類,其key就是待統計的Hashable對象,value就是這些對象的統計計數。Counter對象的 most_common(n) 方法可以很方便地統計出出現次數多的n個key
>>> import collections, random
>>> players =['Wilt Chamberlain', 'Michael Jordan', 'Kareem Abdul-Jabbar', 'Earvin Johnson', 'Kobe Bryant',
'LeBron James', 'Stephen Curry', 'Bill Russell')
>>> for i in range(100):
votesForVIP += [random.choice(players)] * random.randrange(10,20)
>>> len(votesForVIP)
1433
>>> counter = collections.Counter(votesForVIP)
>>> counter
Counter({'Kobe Bryant': 233, 'Kareem Abdul-Jabbar': 210, 'Bill Russell': 196, 'Stephen Curry': 183,
'Michael Jordan': 178, 'Earvin Johnson': 155, 'LeBron James': 139, 'Wilt Chamberlain': 139})
>>> counter.most_common(3)
[('Kobe Bryant', 233), ('Kareem Abdul-Jabbar', 210), ('Bill Russell', 196)]
-
用戶自定義類dict的class的時候,通常不要直接以dict作為父類,這是因為在覆蓋重寫dict類的 get(k, default)、__setitem__( )、__contain__( )、__missing__( ) 等方法時,常常又會使用到 mapObj[k]、 k in mapObj、mapObj[k] 等語法形式,這樣一不小心就會造成這些內部方法的無窮遞歸調用。因此更建議使用 collections.UserDict 類而非dict來作為自定義mapping的父類。
collections.UserDict 名字中包含"dict",但是它并不是dict的子類,而是 collections.MutableMapping 的子類,因此UserDict類也繼承了 __getitem__( )、__contain__( )、__setitem__( )等方法。UserDict類與dict的關聯是通過UserDict對象中包含一個dict類型的成員變量 data 來實現的,data就作為真正的dict數據內容的保存地。用戶自定義類dict class覆蓋重寫這些方法的時候,并不會遞歸調用UserDict類中其他的方法,而是對UserDict.data 變量進行相關操作,從而大大減輕了用戶自定義類時對于死循環遞歸的防范難度,如下示例:
import collections
class StrKeyDict(collections.UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key): # 對self.data操作不會導致自身的 __contains__ 函數的遞歸調用
return str(key) in self.data
def __setitem__(self, key, item): # 對self.data操作不會導致自身的 __setitem__ 函數的遞歸調用
self.data[str(key)] = item
set 和 frozenset 中每一個元素都是唯一而不重復的,這樣的唯一性正是通過每個元素對象的hash值唯一來保證的,因此:
(1)、set和frozenset中每一個元素對象必須是Hashable對象
(2)、set本身不是Hashable對象(因為set是mutable對象)
(3)、frozenset本身是immutable 對象,因此其本身也是Hashable對象set和frozenset的定義方式包括:
>>> # 直接通過iterable對象來構建
>>> s = set([1,2,3,4,5,5,4,3,2,1,6,7,8,9,0,9,8,7,6])
>>> s
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> # 對于所有元素都是直接常量值的set定義
>>> s = {1,2,3,4,5,6,5,4,3,2,1,0,9,8,7,6}
>>> s
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> # 但是定義空的set只能使用 set() 的方式,否則 {} 將會被當做是空的dict而非空的set
>>> empty_set = set(); type(empty_set)
<type 'set'>
>>> empty_dict = {}; type(empty_dict)
<type 'dict'>
>>> # 使用 set comprehensions 推導方式,
>>> # 與dictcomps語法都是使用花括號{ },區別在于dictcomps使用 {key:value for ...} 形式而setcomps使用 {value for ... }形式
>>> from unicodedata import name
# 對于Python 2.7
>>> {unichr(i) for i in range(32,256) if 'SIGN' in name(unichr(i), "")}
set([u'#', u'\xa2', u'%', u'$', u'\xa7', u'\xb0', u'\xa9', u'+', u'\xf7', u'\xa3', u'\xac', u'\xb1', u'\xa4', u'\xb5', u'\xd7',
u'\xb6', u'\xa5', u'\xae', u'=', u'<', u'>'])
#對于Python 3
>>> {chr(i) for i in range(32,256) if 'SIGN' in name(chr(i), "")}
{'?', '<', '>', '¥', '?', '§', '=', '#', '¢', '£', 'μ', '?', '¤', '+', '?', '÷', '%', '°', '$', '×', '±'}
# fronzenset只有一種定義方式:
>>> fs = frozenset([1,3,5,7,9])
- set 在數學上對應著集合的概念,因此除了基本的加減運算之外,數學上的集合運算(and, or, xor, sub等)對于set對象也是完全支持的。
>>> set1 = {i for i in range(20)}; set1
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>> set2 = {i for i in range(10) if i%2==0}; set2
set([0, 8, 2, 4, 6])
>>> set3 = {i for i in range(10) if i%2!=0}; set3
set([1, 3, 9, 5, 7])
>>> set4 = {i for i in range(20) if i%2==0}; set4
set([0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
>>> set5 = {i for i in range(20) if i%2!=0}; set5
set([1, 3, 5, 7, 9, 11, 13, 15, 17, 19])
>>>
>>> set1 & set2
set([0, 8, 2, 4, 6])
>>> set1 ^ set2 # set1和set2中各自包含但是對方不包含的元素的集合
set([1, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>> set2 | set3
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>>
>>> set1.intersection(set2, set4) # set1.intersection(set2, set4, ...) 相當于 set1 & set2 & set4 &...
set([0, 8, 2, 4, 6])
>>> set1.intersection(set2, set3)
set([])
>>> set2.union(set3)
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> set2.union(set3, set4) # set2.union(set3, set4, ...) 相當于 set2 | set3 | set4 | ...
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18])
>>>
>>> set1 - set2
set([1, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>> set2 - set1 # 因為set2是set1的子集,所以set2 - set1的結果為空
set([])
>>> set1.difference(set2, set3) # set1.difference(set2, set3, ...) 相當于 set1 - set2 - set3 - ...
set([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>> set2.symmetric_difference(set1) # symmetric_difference( ) 函數就是亦或函數,等同于set2 ^ set1
set([1, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>>
>>> set1.isdisjoint(set2) # isdisjoint( ) 函數等同于判斷兩個set的交集是否為空,即 len(set1 & set) == 0
False
>>> set2.isdisjoint(set3)
True