2016-08-23 48 views
3

SO上的常見問題是removing duplicates from a list of lists。由於名單是不可能的,set([[1, 2], [3, 4], [1, 2]])投擲TypeError: unhashable type: 'list'。這類問題的答案通常涉及使用元組,這些元組是不可變的,因此可排除。爲什麼讓列表不可用?

這個答案​​包括以下內容:

如果它被保存在字典中的特定插槽後的散列值的變化,這將導致不一致的字典。例如,最初該列表將被存儲在位置A處,該位置是基於散列值確定的。如果散列值發生變化,並且如果我們查找列表,我們可能無法在位置A找到它,或者根據新的散列值,我們可能會發現其他一些對象。

,但我不太理解,因爲其他類型的可用於字典鍵就可以沒有問題地改變:

>>> d = {} 
>>> a = 1234 
>>> d[a] = 'foo' 
>>> a += 1 
>>> d[a] = 'bar' 
>>> d 
{1234: 'foo', 1235: 'bar'} 

很明顯,如果a變化值時,它會出現亂碼到字典中的不同位置。 爲什麼對列表有相同的假設危險?爲什麼以下一種不安全的方法來散列列表,因爲當我們需要時,它就是我們所使用的方法?

>>> class my_list(list): 
... def __hash__(self): 
...  return tuple(self).__hash__() 
... 
>>> a = my_list([1, 2]) 
>>> b = my_list([3, 4]) 
>>> c = my_list([1, 2]) 
>>> foo = [a, b, c] 
>>> foo 
[[1, 2], [3, 4], [1, 2]] 
>>> set(foo) 
set([[1, 2], [3, 4]]) 

看來,這解決了set()問題,爲什麼這是一個問題?列表可能是可變的,但它們是有序的,看起來像是哈希需要的所有東西。

+2

你並沒有修改存儲的**鍵**,而是將一個**新對象**分配給'a'。 –

+3

這些都不一樣。在第一種情況下,您只需使用* * * *對象:1235而不是1234.它不是同一個對象發生了變異,列表中就是這種情況。 –

+0

可能的重複[什麼使列表不可用?](http://stackoverflow.com/questions/23268899/what-makes-lists-unhashable) –

回答

10

你似乎混淆了可變性與重新綁定。 a += 1分配新對象,int對象的數值1235到01​​。在引擎蓋下,對於像int,a += 1這樣的不可變對象,與a = a + 1相同。

原始1234對象沒有發生變異。該字典仍在使用數字值爲1234的int對象作爲鍵。儘管a現在引用了一個不同的對象,該字典仍然保存着該對象的引用。這兩個參考是獨立的。

試試這個:

>>> class BadKey: 
...  def __init__(self, value): 
...   self.value = value 
...  def __eq__(self, other): 
...   return other == self.value 
...  def __hash__(self): 
...   return hash(self.value) 
...  def __repr__(self): 
...   return 'BadKey({!r})'.format(self.value) 
... 
>>> badkey = BadKey('foo') 
>>> d = {badkey: 42} 
>>> badkey.value = 'bar' 
>>> print(d) 
{BadKey('bar'): 42} 

注意,我改變了屬性valuebadkey實例。我甚至沒有碰字典。字典反映了變化; 實際關鍵值本身發生了變異,該對象既名稱badkey和字典引用。

但是,你現在可以不再訪問鍵:

>>> badkey in d 
False 
>>> BadKey('bar') in d 
False 
>>> for key in d: 
...  print(key, key in d) 
... 
BadKey('bar') False 

我已經徹底打破我的字典裏,因爲我再也不能可靠地定位的關鍵。

這是因爲BadKey違反可測性原理;散列值必須保持穩定。你只能這樣做,如果你不改變哈希對象的基礎。哈希必須基於任何使兩個實例相等的東西。

對於列表,內容使兩個列表對象相等。你可以改變這些,所以你也不能產生穩定的散列。

+0

很好的解釋,我對關鍵是一個引用感到困惑,我曾經假定它是一個存儲的散列值,它是在散列並完全存儲在字典中時計算的。謝謝! – Will

+1

@ W:是的,因爲密鑰被插入散列表中由散列值(哈希模表大小)確定的槽中,但是因爲這可能導致*衝突*(多個密鑰散列到同一個槽)當你嘗試再次匹配一個鍵時('d [key]','d'中的鍵,d.get(key),del d [key],d.pop(key)),你還需要測試是否相等需要首先匹配密鑰)。所以當你插入一個對象然後*他們的哈希改變*他們在錯誤的地方,你不能再找到那個地方。舊散列的另一個對象將不再測試相等。 –