解讀Python中字典的key都可以是什么
Python字典的key都可以是什么
答
一個對象能不能作為字典的key,就取決于其有沒有__hash__方法。所以所有python自帶類型中,除了list、dict、set和內部至少帶有上述三種類型之一的tuple之外,其余的對象都能當key。
比如數(shù)值/字符串/完全不可變的元祖/函數(shù)(內建或自定義)/類(內建或自定義)/方法/包等等你能拿出手的,不過有的實際意義不高。還有數(shù)值型要注意,因為兩個不同的相等數(shù)字可以有相同的哈希值,比如1和1.0。
解釋
代碼版本:3.6.3;文檔版本:3.6.6
Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys. Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key. You can’t use lists as keys, since lists can be modified in place using index assignments, slice assignments, or methods like append()and extend().
字典的鍵可以是任意不可變類型,需要注意的是tuple元組作為鍵時,其中不能以任何方式包含可變對象。
那。。到底什么樣的是不可變類型呢?不可能給對象專門標注一個屬性是可變類型還是不可變類型啊,這沒有任何其他意義,一定是通過其他途徑實現(xiàn)的。把list當做鍵試一下
a = [1, 2, 3] d = {a: a} ? ? # 第二行報錯: # TypeError: unhashable type: 'list'
報錯說list類型是不可哈希的,噢,原來是靠能不能hash來判斷的,另外文檔下面接著說同一字典中每個鍵都是唯一的,正好每個對象的哈希值也是唯一的,對應的很好。
It is best to think of a dictionary as an unordered set of key: value pairs, with the requirement that the keys are unique (within one dictionary).
查看源代碼可以看到object對象是定義了__hash__方法的,
而list、set和dict都把__hash__賦值為None了
# 部分源碼 ? class object: ? ? """ The most base type """ ? ? ? def __hash__(self, *args, **kwargs): ?# real signature unknown ? ? ? ? """ Return hash(self). """ ? ? ? ? pass ? ? class list(object): ? ? __hash__ = None ? ? class set(object): ? ? __hash__ = None ? ? class dict(object): ? ? __hash__ = None
那這樣的話。。。我給他加一個hash不就能當字典的key了,key不就是可變的了。
注意
此處只是我跟著想法隨便試,真的應用場景不要用可變類型作為字典的key。
class MyList(list): ? ? """比普通的list多一個__hash__方法""" ? ? ? def __hash__(self): ? ? ? ? # 不能返回hash(self) ? ? ? ? # hash(self)會調用self的本方法,再調用回去,那就沒完了(RecursionError) ? ? ? ? # 用的時候要注意實例中至少有一個元素,不然0怎么取(IndexError) ? ? ? ? return hash(self[0]) ? ? l1 = MyList([1, 2]) ?# print(l1) -> [1, 2] d = {l1: 'Can?'} print(d) ?# --> ?{[1, 2]: 'Can?'} l1.append(3) print(d) ?# {[1, 2, 3]: 'Can?'} print(d[l1]) ?# --> ?Can?
到這里就可以肯定的說,一個對象能不能作為字典的key,就取決于其有沒有__hash__方法。所以所有python自帶類型中,目前我已知的除了list、dict、set和內部帶有以上三種類型的tuple之外,其余的對象都能當key。而我們自己定義的類,一般情況下都直接間接的和object有關,都帶有__hash__方法。
另外我想到,既然字典的鍵是唯一的,而哈希值也是唯一的,這么巧,鍵的唯一性不會就是用哈希值來確定的吧?我上一個例子中__hash__方法返回的是0號元素的哈希值,那我直接用相同哈希值的對象是不是就能改變那本來不屬于它的字典值呢?
class MyList(list): ? ? def __hash__(self): ? ? ? ? return hash(self[0]) ? ? l1 = MyList([1, 2]) ?# print(l1) -> [1, 2] d = {} d[l1] = l1 print(d) ?# {[1, 2]: [1, 2]} d[1] = 1 print(d) ?# {[1, 2]: [1, 2], 1: 1}
竟然沒有改成功而是新添加了一個鍵值對,可self[0]就是1啊,哈希值一樣啊,怎么會不一樣呢?難道要鍵的值一樣才能判斷是同一個鍵嗎?重寫__eq__方法試一下。
class MyList(list): ? ? def __hash__(self): ? ? ? ? return hash(self[0]) ? ? ? def __eq__(self, other): ? ? ? ? return self[0] == other ? ? l1 = MyList([1, 2]) ?# print(l1) -> [1, 2] d = {} d[l1] = l1 print(d) ?# {[1, 2]: [1, 2]} d[1] = 1 print(d) ?# {[1, 2]: 1}
這回成功了,那就是__hash__返回值相等,且eq判斷也相等,才會被認為是同一個鍵。那這兩個先判斷哪個呢?加個代碼試一下
class MyList(list): ? ? def __hash__(self): ? ? ? ? print('hash is run') ? ? ? ? return hash(self[0]) ? ? ? def __eq__(self, other): ? ? ? ? print('eq is run') ? ? ? ? return self[0] == other ? ? l1 = MyList([1, 2]) ?# print(l1) -> [1, 2] d = {} d[1] = 1 d[l1] = 'l1' print(d) ? ? # 結果: # hash is run # eq is run # {1: 'l1'}
__hash__先執(zhí)行,另外字典在內存中存儲數(shù)據(jù)的位置和鍵的hash也是有關的,邏輯上也像印證。
先計算hash,找到相對應的那片內存空間,里面沒有值的話就直接寫入,對于字典來說就是新增鍵值對;如果里面已經有值了,那就判斷新來的鍵和原來的那里的鍵是不是相等,相等就認為是一個鍵,對于字典來說就是更新值,不相等就再開空間,相當于字典新增鍵值對。
在你驗證自己想法的時候可能遇到__hash__和__eq__的一些想不到的麻煩,可以看這里:__hash__和__eq__的繼承使用問題
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Python+DeOldify實現(xiàn)老照片上色功能
DeOldify是一種技術,以彩色和恢復舊的黑白圖像,甚至電影片段。它是由一個叫Jason?Antic的人開發(fā)和更新的。本文將利用DeOldify實現(xiàn)老照片上色功能,感興趣的可以了解一下2022-06-06python之lambda表達式與sort函數(shù)中的key用法
這篇文章主要介紹了python之lambda表達式與sort函數(shù)中的key用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08python輸入整條數(shù)據(jù)分割存入數(shù)組的方法
今天小編就為大家分享一篇python輸入整條數(shù)據(jù)分割存入數(shù)組的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11