Объект пользовательского типа в качестве ключа словаря - PullRequest
170 голосов
/ 04 февраля 2011

Что я должен сделать, чтобы использовать мои объекты пользовательского типа в качестве ключей в словаре Python (где я не хочу, чтобы «идентификатор объекта» выступал в качестве ключа), например,

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

Я бы хотел использовать MyThing в качестве ключей, которые считаются одинаковыми, если имя и местоположение совпадают. Из C # / Java я привык переопределять и предоставлять метод равенства и хэш-кода и обещал не изменять ничего, от чего зависит хэш-код.

Что я должен сделать в Python для этого? Должен ли я даже?

(В простом случае, как здесь, возможно, было бы лучше просто поместить кортеж (имя, местоположение) в качестве ключа - но учтите, что я бы хотел, чтобы ключ был объектом)

Ответы [ 3 ]

200 голосов
/ 04 февраля 2011

Вам необходимо добавить 2 метода , примечание __hash__ и __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)

Документация Python dict определяет эти требования к ключевым объектам, т. Е. Они должны быть hashable .

30 голосов
/ 04 февраля 2011

Альтернативой в Python 2.6 или выше является использование collections.namedtuple() - это избавляет вас от написания любых специальных методов:

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
20 голосов
/ 04 февраля 2011

Вы переопределяете __hash__, если вам нужна особая хеш-семантика, и __cmp__ или __eq__, чтобы сделать ваш класс пригодным для использования в качестве ключа.Объекты, сравнивающие равные, должны иметь одинаковое хеш-значение.

Python ожидает, что __hash__ вернет целое число, возвращать Banana() не рекомендуется :)

Пользовательские классы имеют __hash__по умолчанию это вызывает id(self), как вы заметили.

Есть несколько дополнительных советов из документации .:

Классы, которые наследуют __hash__()метод из родительского класса, но измените значение __cmp__() или __eq__() таким образом, чтобы возвращаемое значение хеша больше не подходило (например, путем переключения на основанную на значении концепцию равенства вместо равенства по умолчанию на основе идентичности) может явно пометитьСебя нельзя отрицать, установив __hash__ = None в определении класса.Это означает, что экземпляры класса не только вызовут соответствующий TypeError, когда программа попытается получить их хэш-значение, но они также будут правильно определены как не подлежащие обработке при проверке isinstance(obj, collections.Hashable) (в отличие от классов, которые определяют свои собственные __hash__() дляявно вызвать TypeError).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...