Obiekt typu własnego jako klucz słownikowy

Co muszę zrobić, aby używać obiektów niestandardowego typu jako kluczy w słowniku Pythona ( gdzie nie chcę, aby "object id" działał jako klucz), np.

class MyThing:
    def __init__(self,name,location,length):
            self.name = name
            self.location = location
            self.length = length

Chciałbym użyć MyThing ' s jako kluczy, które są uważane za te same, jeśli nazwa i lokalizacja są takie same. Od C # / Java jestem przyzwyczajony do nadpisywania i dostarczania metody equals i hashcode, i obiecuję nie mutować niczego, od czego zależy hashcode.

Co muszę zrobić w Pythonie, aby to osiągnąć ? Powinienem w ogóle ?

(w prostym przypadku, jak tutaj, może lepiej byłoby umieścić krotkę (nazwę, lokalizację) jako klucz - ale pomyśl, że chciałbym, aby klucz był obiektem)

Author: aknuds1, 2011-02-04

3 answers

Należy dodać 2 metody , notatkę __hash__ i __eq__:

class MyThing:
    def __init__(self,name,location,length):
        self.name = name
        self.location = location
        self.length = length

    def __hash__(self):
        return hash((self.name, self.location))

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

    def __ne__(self, other):
        # Not strictly necessary, but to avoid having both x==y and x!=y
        # True at the same time
        return not(self == other)

Dokumentacja Pythona dict definiuje te wymagania na obiektach kluczowych, tzn. muszą być hashable .

 183
Author: 6502,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-18 04:13:18

Alternatywą w Pythonie 2.6 lub nowszym jest użycie collections.namedtuple() -- zapisuje pisanie żadnych specjalnych metod:

from collections import namedtuple
MyThingBase = namedtuple("MyThingBase", ["name", "location"])
class MyThing(MyThingBase):
    def __new__(cls, name, location, length):
        obj = MyThingBase.__new__(cls, name, location)
        obj.length = length
        return obj

a = MyThing("a", "here", 10)
b = MyThing("a", "here", 20)
c = MyThing("c", "there", 10)
a == b
# True
hash(a) == hash(b)
# True
a == c
# False
 30
Author: Sven Marnach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-04 20:58:30

Nadpisujesz __hash__, jeśli chcesz użyć specjalnej semantyki hashowej i __cmp__ lub __eq__, aby Twoja klasa była użyteczna jako klucz. Obiekty, które porównują równe muszą mieć tę samą wartość skrótu.

Python oczekuje, że __hash__ zwróci liczbę całkowitą, zwracanie Banana() nie jest zalecane:)

Klasy zdefiniowane przez Użytkownika mają __hash__ domyślnie wywołujące id(self), jak zauważyłeś.

Jest kilka dodatkowych wskazówek z dokumentacji.:

Klasy dziedziczące a __hash__() metoda z klasy rodzica, ale zmiana znaczenie __cmp__() lub __eq__() taki, że zwrócona wartość hash jest już nie odpowiednie (np. przez przejście na koncepcję opartą na wartościach równość zamiast domyślnej równość tożsamościowa) może wyraźnie zaznaczają się jako unhashable by setting __hash__ = None w definicji klasy. W ten sposób oznacza, że nie tylko Klasa podnosi odpowiednie TypeError gdy program próbuje odzyskać ich wartość hash, ale oni zostanie również poprawnie zidentyfikowany jako nieszkodliwe podczas sprawdzania isinstance(obj, collections.Hashable) (w przeciwieństwie do klas, które definiują własne __hash__() jawnie podnieść TypeError).

 19
Author: Skurmedel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-04 19:44:46