2016-07-27 41 views
3

閱讀this answer看來,如果__eq__是在自定義類中定義的,則還需要定義__hash__。這是可以理解的。
但是目前尚不清楚,爲什麼 - 有效 - __eq__應該是相同self.__hash__()==other.__hash__真的有必要對相同的類進行哈希處理嗎?

想象這樣一個類:

class Foo: 
    ... 
    self.Name 
    self.Value 
    ... 
    def __eq__(self,other): 
     return self.Value==other.Value 
    ... 
    def __hash__(self): 
     return id(self.Name) 

這樣的類實例可以通過值進行比較,這可能是唯一的合理使用,但被認爲是相同的名稱。
這種方式set不能包含名稱相同的多個實例,但比較仍然有效。

這樣的定義有什麼問題?

原因由Value定義__eq____lt__等是能夠通過Value以實例進行排序,並能夠使用功能,如最大值。例如,他的班級應代表設備的物理輸出(如加熱元件)。每個輸出都有唯一的名稱。值是輸出設備的功率。爲了找到加熱元件的最佳組合以便開啓,能夠通過功率(值)比較它們是有用的。但是,在一個集合或字典中,不應該有多個名稱相同的輸出。當然,不同名字的不同產出可能很容易擁有同等權力。

+0

如果是'Value'這是平等重要的是,你一般實行'__hash__'爲'返回散列(self.Value)'。 – jonrsharpe

+0

在這種情況下,值不足以識別身份,僅適用於大小。通過值__eq_和_lt_以及其他值一起定義它是允許類可以按值排序的方式,但具有相同值的實例不會被視爲相同。 – Lukas

+0

你可以給一個不太抽象的例子嗎?目前這個問題似乎是你的班級沒有被一致定義。 – jonrsharpe

回答

5

問題在於它沒有意義,散列用於執行對象的有效分區。因此,當你有一個散列表實現的集合時,每個散列指向一個存儲區,通常是一個元素列表。爲了檢查一個元素是否在集合(或其他基於散列的容器)中,您轉到由散列指向的桶,然後遍歷列表中的所有元素,逐個比較它們。

換句話說 - 哈希不應該是一個比較器(因爲它可以,並應該給你有時誤報)。特別是,在你的例子中,你的設置不起作用 - 它不會識別重複,因爲它們不會相互比較。

class Foo: 

    def __eq__(self,other): 
     return self.Value==other.Value 

    def __hash__(self): 
     return id(self.Name) 


a = set() 
el = Foo() 
el.Name = 'x' 
el.Value = 1 

el2 = Foo() 
el2.Name = 'x' 
el2.Value = 2 

a.add(el) 
a.add(el2) 
print len(a) # should be 1, right? Well it is 2 

實際上它是更糟糕的則是,如果你有2個對象具有相同的值,但不同的名稱,他們不認爲是相同的或者

class Foo: 

    def __eq__(self,other): 
     return self.Value==other.Value 

    def __hash__(self): 
     return id(self.Name) 


a = set() 
el = Foo() 
el.Name = 'x' 
el.Value = 2 

el2 = Foo() 
el2.Name = 'a' 
el2.Value = 2 

a.add(el) 
a.add(el2) 
print len(a) # should be 1, right? Well it is 2 again 

同時適當做(這樣「如果== b,則散列的(a)==散列(b)」)給出:

class Foo: 

    def __eq__(self,other): 
     return self.Name==other.Name 

    def __hash__(self): 
     return id(self.Name) 


a = set() 
el = Foo() 
el.Name = 'x' 
el.Value = 1 

el2 = Foo() 
el2.Name = 'x' 
el2.Value = 2 

a.add(el) 
a.add(el2) 
print len(a) # is really 1 

更新

有一個也是一個非確定性的部分,這是很難輕易再現,但基本上哈希並不唯一定義一個桶。通常它就像

bucket_id = hash(object) % size_of_allocated_memory 

因此,具有不同哈希的東西仍然可能在同一個桶中結束。因此,即使名稱是不同的,也可以通過其他方式取決於實際的內部實現,內存約束等,因此可以獲得與每個值(內部集合)相等的兩個元素。

一般有更多的例子,事情可以去錯了,因爲哈希是定義爲功能h : X -> Z這樣x == y => h(x) == h(y),這樣的人實現自己的容器,授權協議和其他工具都是免費的,以承擔這屬性。如果你破壞它 - 每一個使用哈希的工具都可能中斷。此外,它可以在時間打破,這意味着你更新一些庫,你的代碼將停止工作,作爲一個有效的更新底層庫(使用上述的前提下),可能會導致利用你違反這個假設。

更新2

最後,爲了解決您的問題 - 你根本不應該定義你EQLT運營商來處理排序。它大約是元素的實際比較,它應該與其餘行爲兼容。所有你需要做的就是定義一個單獨的比較並在排序程序使用它(在Python分揀接受任何比較,你並不需要依靠<,>等)。另一種方法是改爲有效的<,>,=定義在值上,但爲了保持名稱的唯一性 - 保留一個集合,包括......好的名稱,而不是對象本身。無論你選擇哪條路徑 - 這裏的關鍵要素是: 等號和散列必須兼容,就是這樣。

+0

您所描述的第一個問題是我想有什麼。 – Lukas

+0

這是一個有點棘手,顯示一個例子,當它吹起來,但你可以實際上有不同的名字,它們將被放置在同一個桶中,然後由於值相等,你會得到誤報。「id」應該是唯一的並不意味着實際的桶將是唯一的。換句話說 - 第一個例子是**非確定性**。它有時會給你平等的成分,有時候不會。取決於python解釋器的內部實現,可用內存等。 – lejlot

+1

@Lukas現在你自相矛盾了。在這個問題中,你寫了_「然而,在一個集合或詞典中,應該不可能有多個具有相同名稱的輸出」_。 –

-1

這是可以實現這樣和有任何問題,你的類。但是,您必須100%確定沒有兩個不同的對象會生成相同的散列。請看下面的例子:

class Foo: 
    def __init__(self, name, value): 
     self.name= name 
     self.value= value 

    def __eq__(self, other): 
     return self.value == other.value 

    def __hash__(self): 
     return hash(self.name[0]) 

s= set() 
s.add(Foo('a', 1)) 
s.add(Foo('b', 1)) 
print(len(s)) # output: 2 

但如果發生哈希衝突,你有一個問題:

s.add(Foo('abc', 1)) 
print(len(s)) # output: 2 

爲了防止這一點,你必須知道準確哈希值是如何生成的(其中,如果依靠像idhash功能,可能會在實現之間有所不同!)以及用於生成散列(name在這個例子中)的屬性(S)也值。這就是爲什麼排除哈希碰撞的可能性非常困難,即使不是不可能的原因。這基本上像乞求意想不到的事情發生。

+0

這根本不是真的。哈希不需要唯一標識任何桶,哈希表(和其他容器)可以(也可以)對你的哈希模塊進行模操作(或其他映射到恆定大小的容器),因此即使兩個獨特的哈希可能最終被分爲同樣的地方,雖然這可以在容器層面上解決(你可以在迭代時比較哈希),但沒有必要這樣做,因爲「常規」平等應該工作 – lejlot

+0

@lejlot所以這意味着更難避免哈希碰撞比我說的。這並不意味着我所說的是錯誤的。 –

+0

這不是一個哈希碰撞。這是兩件不同的事情。哈希碰撞,就像在你的答案中一樣,當你有x!= y這樣h(x)== h(y)。我所描述的是在實踐中使用哈希時可能遇到的一種錯誤。這些錯誤來自這樣一個事實,即(如前面的回答中所述)存在一個關於哈希的潛在假設,即如果兩個對象相等,則它們的哈希值也相等。期。這不是關於碰撞,而是關於散列之後可能(並且將在某個時刻)被破壞的一個通用邏輯。 – lejlot

相關問題