2012-06-12 19 views
34

Oto mój kod:Jak poprawnie dopasować obiekt?

class Hero: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __str__(self): 
     return self.name + str(self.age) 

    def __hash__(self): 
     print(hash(str(self))) 
     return hash(str(self)) 

heroes = set() 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 1 

heroes.add(Hero('Lara Miheenko', 17)) # gets hash -2822451113328084695 
print(len(heroes)) # gets 2 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 3! WHY? 

Dlaczego tak się dzieje?
Pierwszy i trzeci obiekt mają tę samą zawartość i ten sam skrót, ale len() mówi o 3 unikalnych obiektach?

+0

Nie jestem pewien, ale prawdopodobnie trzeba swój '__eq__' lub' __cmp__' : http://docs.python.org/glossary.html#term-hashable – nhahtdh

+2

Poza tym nie jest to najlepsza funkcja skrótu (ponieważ nie dodajesz ogólnego ciągu znaków, jeden z komponentów ciągu ma dużo niższą entropię ponieważ wiadomo, że składa się z cyfr). Dla trywialnej, ale dość skutecznej poprawki, pobierz wartości mieszania obiektów oddzielnie i xoruj je. Aby uzyskać więcej magii, dodaj je skalowane przez stałą liczbę pierwszą. –

+0

@KonradRudolph: W twoim komentarzu kryje się domyślne założenie - konkretnie, że dobry "hasz" jest potrzebny, aby zestaw dobrze działał. Tak nie jest w przypadku implementacji "set" Pythona; patrz [ten komentarz ze źródeł Pythona] (http://hg.python.org/cpython/file/26e2ee402a0b/Objects/dictobject.c#l113) w celu uzyskania dalszych wyjaśnień. –

Odpowiedz

40

Musisz również zdefiniować __eq__() w kompatybilny sposób z __hash__() - w przeciwnym razie równość będzie oparta na tożsamości obiektu.

W Pythonie 2 zaleca się także zdefiniowanie __ne__, aby uczynić != spójnym z ==. W Pythonie 3 domyślna implementacja __ne__ przeniesie Cię na __eq__.

+6

Rzeczywiście, po sprawdzeniu, czy skróty są równe, 'dict' /' set' musi również sprawdzić rzeczywistą równość w przypadku kolizji mieszania. –

+0

@ user2357112 Definicja '__ne__' nie jest wymagana, aby utworzyć formatowanie, prawda? Dobrym pomysłem może być zdefiniowanie go, ponieważ inaczej '! =' Będzie miało raczej dziwną semantykę, ale jeśli wszystko, co chcesz zrobić, to użycie typu w zestawie lub słowniku, tak naprawdę nie potrzebujesz tego. –

+0

@SvenMarnach: Technicznie, zestawy i dyktury nie używają '! =' Gdziekolwiek, ale w rzeczywistości polegając na tym jest receptą na nieprzyjemne błędy. Nawet jeśli zestawy nie używają '! =', Prawdopodobnie ktoś to zrobi. Możesz go zmienić, jeśli chcesz, ale myślę, że odpowiedź powinna zdecydowanie wspomnieć o "__ne__"; duch pytania zdecydowanie wydaje się bardziej "jak robić rzeczy * w prawo *" niż "jakie jest absolutne minimum potrzebne do uzyskania tego fragmentu kodu, aby uzyskać oczekiwany wynik". – user2357112

6

The Python documentation może być pomocne:

Jeśli klasa nie definiuje __cmp__() lub __eq__() metody nie powinien zdefiniować operację __hash__() albo;

8

Oto cały kod:

class Hero: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __str__(self): 
     return self.name + str(self.age) 

    def __hash__(self): 
     print(hash(str(self))) 
     return hash(str(self)) 

    def __eq__(self,other): 
     return self.name == other.name and self.age== other.age 



heroes = set() 
heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 1 

heroes.add(Hero('Lara Miheenko', 17)) # gets hash -2822451113328084695 
print(len(heroes)) # gets 2 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 2 

Funkcja rozpoznaje __eq__ i jako taki len to 2.