Объект пользовательского типа в качестве словарного ключа
Что я должен сделать, чтобы использовать мои объекты пользовательского типа в качестве ключей в словаре Python (где я не хочу, чтобы "идентификатор объекта" выступал в качестве ключа), например.
class MyThing:
def __init__(self,name,location,length):
self.name = name
self.location = location
self.length = length
Я бы хотел использовать MyThing как ключи, которые считаются одинаковыми, если имя и местоположение совпадают.
Из С#/Java я привык к переопределению и предоставлению метода equals и hashcode и обещаю не мутировать ничего, на что зависит хэш-код.
Что я должен сделать в Python для этого? Должен ли я даже?
(В простом случае, как здесь, возможно, было бы лучше просто поместить (имя, местоположение) кортеж в качестве ключа, но подумайте, что я хочу, чтобы ключ был объектом)
Ответы
Ответ 1
Вам нужно добавить 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.
Ответ 2
Альтернативой в 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
Ответ 3
Вы переопределяете __hash__
, если вы хотите использовать специальную хэш-семантику и __cmp__
или __eq__
, чтобы сделать ваш класс пригодным для использования в качестве ключа. Объекты, которые сравнивают одинаковые, должны иметь одинаковое значение хэша.
Python ожидает, что __hash__
вернет целое число, возврат Banana()
не рекомендуется:)
Пользовательские классы имеют __hash__
по умолчанию, который вызывает id(self)
, как вы отметили.
В документации есть дополнительные советы:
Классы, наследующие a __hash__()
метод из родительского класса, но изменение значение __cmp__()
или __eq__()
так что возвращаемое значение хэша больше не подходит (например, переход на концепцию, основанную на ценности равенство вместо дефолта равенство идентичности) может явно обозначают себя как отключить настройку __hash__ = None
в определении класса. Делать это означает, что не только будут случаи класс поднимет соответствующий TypeError, когда программа пытается получить их хэш-значение, но они также будут правильно определены как расчесывать при проверке isinstance(obj, collections.Hashable)
(в отличие от классов, которые определяют их собственные __hash__()
, чтобы явно поднять TypeError).