字典dict是Python中使用頻率很是高的數據結構,關於它的使用,也有許多的小技巧,掌握這些小技巧會讓你高效地的使用dict,也會讓你的代碼更簡潔.html
1.默認值python
假設name_for_userid存放的是name和id的映射關係:json
name_for_userid = { 1: '張三', 2: '李四', 3: '王五', }
獲取name_for_userid中的某一個id的name,最簡單的方式:數組
name_for_userid[1]
'張三'
這種方式雖然簡單,但有一個不便之處就是,若是獲取一個不存在於name_for_userid中的值程序會出現異常:數據結構
name_for_userid[4] --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-138-66c7bf5cd53a> in <module>() ----> 1 name_for_userid[4] KeyError: 4
不少時候咱們不但願程序出現這種異常,爲了不這種狀況,能夠在獲取name以前先判斷該id是否已經存在name_for_userid中:app
if 4 in name_for_userid: print(name_for_userid[4]) else: print("Not Found")
"Not Found"
這種寫法雖然可行,可是效率不高,由於獲取某一個id對應的name最多須要查詢兩次:第一次是先判斷該id是否存在name_for_userid中,若存在則第二次從name_for_userid中取出name值。還有一種寫法:函數
try: print(name_for_userid[4]) except KeyError: print("Not Found")
"Not Found"
這種寫法代碼有些冗餘,不夠簡潔,因此,dict提供了get方法,這個方法的好處就是能夠設值default值,對於那些不存在於dict中的key會返回default值做爲它的value:性能
name_for_userid.get(4,"None")
"None"
2.排序
ui
card = {'a': 4, 'c': 2, 'b': 3, 'd': 1}
用sorted對card排序,實際上是用card的key進行排序,以下:spa
sorted(card.items())
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]
有些時候咱們須要對card的value進行排序,這個時候就可使用sorted函數中的key這個參數,咱們能夠help一下這個函數的用法:
help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, key=None, reverse=False) Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customise the sort order, and the reverse flag can be set to request the result in descending order.
因此咱們能夠自定義一個key函數sorted_by_value:
def sorted_by_value(item): return item[1] sorted(card.items(),key=sorted_by_value)
[('d', 1), ('c', 2), ('b', 3), ('a', 4)]
其實,若是使用lambda匿名函數的話,代碼會更簡潔:
sorted(card.items(),key=lambda item:item[1])
若是讓別人更輕鬆的理解你的意圖,你能夠嘗試用下面的方法:
import operator sorted(card.items(),key=operator.itemgetter(1))
須要注意的是,operator.itemgetter函數獲取的不是值,而是定義了一個函數,經過該函數做用到對象上才能獲取該對象上指定域的值。
其實,仍是比較喜歡lambda表達式,由於它靈活,簡潔,好比,要對依據value的絕對值對dict排序,就能夠這樣寫:
sorted(card.items(),key=lambda item:abs(item[1]))
3.switch...case
有句老話是這樣說的,Python過去如今以及之後都不會有switch...case語句。這是由於if…elif…else和dict都能實現switch...case語句,因此switch...case就沒有存在的必要了。
if cond == 'cond_a': handle_a() elif cond == 'cond_b': handle_b() else: handle_default()
用if...elif...else來實現switch的功能,好處就是可讀性強,可是若是處理的條件比較多,這樣的寫法就有些囉嗦和冗餘。因此,這個時候dict就又閃亮登場了。
func_dict = { 'cond_a': handle_a, 'cond_b': handle_b }
cond = 'cond_a'
func_dict[cond]()
相對於if...elif...else,dict就顯得清爽了許多,另外,若是想要實現default咱們也可使用dict的get()方法:
>>>func_dict.get(cond, handle_default)()
這樣即便cond不在func_dict中,程序也不會異常停止。
下面再舉一個例子,以下
>>>def dispatch_if(operator, x, y):
if operator == 'add':
return x + y
elif operator == 'sub':
return x - y
elif operator == 'mul':
return x * y
elif operator == 'div':
return x / y
>>>def dispatch_dict(operator, x, y): return { 'add': lambda: x + y, 'sub': lambda: x - y, 'mul': lambda: x * y, 'div': lambda: x / y, }.get(operator, lambda: None)()
若是想的再深刻點的話,上面的例子就性能上來講不是最優的,由於,每次調用dispatch_dict函數的時候都會生成一個臨時的包含各類操做碼(加減乘除)的字典,最好的狀況固然是這個字典只生成一次(常量),下次再調用此函數的時候,直接使用此字典就能夠了,另外,python中的operator模塊已經實現了加減乘除如:operator.mul, operator.div ,徹底能夠替代lambda表達式。這裏只是舉個例子以更好的明白if…elif…else和dict的異同。
4.合併dict的幾種方法
有的時候須要用一個dict去更新另外一個dict,好比,用戶自定義的配置文件去覆蓋默認配置,等等。假設有下面兩個dict:
>>> xs = {'a': 1, 'b': 2} >>> ys = {'b': 3, 'c': 4}
最經常使用的就是用dict內置的update()方法:
>>> zs = {} >>> zs.update(xs) >>> zs.update(ys)
>>> zs
{'a': 1, 'b': 3, 'c': 4}
還有一種方法就是使用內置的dict():
>>> zs = {**xs, **ys} {'a': 1, 'b': 3, 'c': 4}
5.美觀打印
當打印一些調試信息的時候,美觀的打印輸出有的時候能讓人很直觀的看出關鍵信息,提升調試的效率。
最樸素的打印:
>>> mapping = {'a': 23, 'b': 42, 'c': 0xc0ffee} >>> str(mapping) "{'c': 12648430, 'a': 23, 'b': 42}"
藉助內置模塊json能夠實現更加直觀的表現形式:
>>> import json >>> json.dumps(mapping, indent=4, sort_keys=True) { "a": 23, "b": 42, "c": 12648430 }
可是這種方法有必定的限制:
>>> mapping['d'] = {1, 2, 3} >>> json.dumps(mapping) TypeError: "set([1, 2, 3]) is not JSON serializable" >>> json.dumps({all: 'yup'}) TypeError: "keys must be a string"
因此,還可使用下面的方式:
>>> import pprint >>> pprint.pprint(mapping) {'a': 23, 'b': 42, 'c': 12648430, 'd': set([1, 2, 3])}
6.彩蛋
最後介紹幾個關於dict比較有意思的東西 .
>>>print("keys:",list({True: 'yes', 1: 'no', 1.0: 'maybe'}.keys())) >>>print("values:",list({True: 'yes', 1: 'no', 1.0: 'maybe'}.values()))
上面的代碼會打印出什麼結果呢,一開始我認爲是下面的輸出:
keys: [True,1,1.0] values: ['yes','no','maybe']
真是too young too simple,其實結果應是:
keys: [True] values: ['maybe']
why?再仔細想一想的化,既然key是True,那value爲何不是yes而是maybe,再者,既然value是maybe,那key值爲何不是1.0而是True呢?大家的疑問是否是和我同樣?
其實,當Python在處理dict表達式的時候,它會先根據dict類建立出dict的實例,而後按照item(key:value)在dict表達式出現的順序依次賦值,以下:
>>>xs = dict() >>>xs[True] = 'yes' >>>xs[1] = 'no' >>>xs[1.0] = 'maybe'
更奇怪的是,dict會用下面的表達式認爲他的key值都是相等的:
>>>True == 1 == 1.0
True
看到1==1.0你也許不會感到奇怪,可是爲何會有True==1?當時看到這裏我也有點懵逼的感受.
翻閱了一下Python的官方文檔,在這一章節The standard type hierarchy中發現下面這段話:
「The Boolean type is a subtype of the integer type, and Boolean values behave like the values 0 and 1, respectively, in almost all contexts, the exception being that when converted to a string, the strings ‘False’ or ‘True’ are returned, respectively.」
意思就是說,Boolean是Int的子類,就是0和1.那麼咱們能夠驗證一下:
>>>["No","Yes"][True]
結果果真是Yes,
>>>["No","Yes"][False]
結果也果真是No.可是爲了清晰起見,不建議這樣用.
看到這裏你可能會問,這和dict有毛關係.接着往下看:
就Python而言,True,1和1.0都表示相同的字典key值,當解釋器在執行dict表達式的時候,它會把後面的同一個key(True)的value值覆蓋掉這個key(True)以前的value值.因此就會有下面的結果:
>>>{True: 'yes', 1: 'no', 1.0: 'maybe'} {True: 'maybe'}
可是key爲何沒有被最後的1.0覆蓋呢?其實道理也很簡單,既然是同樣的key,爲何還要畫蛇添足再用多餘的時間去更新"相同的"key呢?這是出於CPython解釋器性能的考慮.
>>>ys = {1.0: 'no'} >>>ys[True] = 'yes' >>>ys 輸出:{1.0: 'yes'}
根據咱們如今所瞭解到的,從表面上看是當key值相同時纔會覆蓋掉已有的value值,可是,事實證實,這不只僅和斷定相等的__eq__有關係.
Python的字典是經過哈希表實現的,也就是說,它經過把key映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作哈希函數,存放記錄的數組叫作哈希表。可是沒有十分完美的哈希函數計算出來的存儲位置都是不同的,因此就會存在哈希衝突的狀況,也就是不一樣的key計算出來的存儲位置是同一個地址.下面來看看到底是什麼致使了更新value值.
>>>class AlwaysEquals: def __eq__(self, other): return True def __hash__(self): return id(self)
AlwaysEquals有兩個方法:__eq__和__hash__.
首先,因爲__eq__返回True,因此下面的條件表達式都是True:
>>>AlwaysEquals() == AlwaysEquals()
>>>AlwaysEquals() == 42
>>>AlwaysEquals() == 'waaat?'
其次,因爲__hash__返回該對象的id值,也就是內存地址,因此有:
>>>objects = [AlwaysEquals(),AlwaysEquals(),AlwaysEquals()] >>>[hash(obj) for obj in objects]
[140388524604608, 140388524604664, 140388524604720]
因此,綜上,當值相等而hash值不相等時候,是否會存在value值覆蓋:
>>>{AlwaysEquals(): 'yes', AlwaysEquals(): 'no'}
結果是:
{<__main__.AlwaysEquals at 0x7faec023bbe0>: 'yes',<__main__.AlwaysEquals at 0x7faec023bac8>: 'no'}
>>>class SameHash: def __hash__(self): return 1 >>>a = SameHash() >>>b = SameHash()
因爲__hash__返回1,因此有:
>>> a == b False >>> hash(a), hash(b) (1, 1)
當值不相等而hash值相等的時候,是否會存在value值覆蓋:
>>> {a: 'a', b: 'b'} { <SameHash instance at 0x7f7159020cb0>: 'a',<SameHash instance at 0x7f7159020cf8>: 'b' }
綜上這兩種狀況,value值都不會被更新。
而{True: 'yes', 1: 'no', 1.0: 'maybe'} 會出現更新key所對應的value值,是由於:
>>> True == 1 == 1.0 True >>> (hash(True), hash(1), hash(1.0)) (1, 1, 1)
他們同時知足條件表達式相等且hash值也相等。