Python中字典的key都可以是什麼
作者:Inotime
來源:CSDN
原文:https://blog.csdn.net/lnotime/article/details/81192207
答:一個物件能不能作為字典的key,就取決於其有沒有__hash__方法。所以所有python自帶型別中,除了list、dict、set和內部至少帶有上述三種類型之一的tuple之外,其餘的物件都能當key。
比如數值/字串/完全不可變的元祖/函式(內建或自定義)/類(內建或自定義)/方法/包等等你能拿出手的,不過有的實際意義不高。還有數值型要注意,因為兩個不同的相等數字可以有相同的雜湊值,比如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元組作為鍵時,其中不能以任何方式包含可變物件。
那。。到底什麼樣的是不可變型別呢?不可能給物件專門標註一個屬性是可變型別還是不可變型別啊,這沒有任何其他意義,一定是通過其他途徑實現的。把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__先執行,另外字典在記憶體中儲存資料的位置和鍵的hash也是有關的,邏輯上也像印證。先計算hash,找到相對應的那片記憶體空間,裡面沒有值的話就直接寫入,對於字典來說就是新增鍵值對;如果裡面已經有值了,那就判斷新來的鍵和原來的那裡的鍵是不是相等,相等就認為是一個鍵,對於字典來說就是更新值,不相等就再開空間,相當於字典新增鍵值對。
在你驗證自己想法的時候可能遇到__hash__和__eq__的一些想不到的麻煩,可以看這裡:__hash__和__eq__的繼承使用問題
---------------------