自定义类型的对象作为字典键


185

如何将自定义类型的对象用作Python字典中的键(我不希望“对象id”用作键),例如

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

如果名称和位置相同,我想将MyThing用作相同的键。从C#/ Java开始,我习惯于重写并提供一个equals和hashcode方法,并保证不会突变该hashcode依赖的任何内容。

我必须在Python中做什么才能做到这一点?我应该吗?

(在一个简单的例子中,就像这里一样,也许最好将一个(名称,位置)元组放置为键-但考虑到我希望键成为一个对象)


使用哈希有什么问题?
拉菲·凯特勒

5
可能是因为他需要打两次MyThing,如果他们有相同的namelocation,索引字典中返回相同的值,即使它们分别创建为两个不同的“对象”。
圣诞老人

1
“也许最好将一个(名称,位置)元组放置为键-但考虑到我希望该键成为一个对象)”您的意思是:一个非复合对象?
eyquem 2011年

Answers:


220

您需要添加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文档对关键对象定义了这些要求,即它们必须是可哈希的


17
hash(self.name)看起来比更好self.name.__hash__(),如果可以hash((x, y)),可以避免对自己进行XOR。
Rosh Oxymoron

5
另外请注意,我刚刚发现这样的调用x.__hash__()也是错误的,因为它产生错误的结果:pastebin.com/C9fSH7eF
Rosh Oxymoron

@Rosh Oxymoron:谢谢你的评论。在撰写本文时,我使用的是显式的and__eq__但后来我想到“为什么不使用元组?” 因为无论如何我经常这样做(我认为它更具可读性)。由于某些奇怪的原因,我的眼睛没有回头再问__hash__
6502

1
@ user877329:您是否要使用某些Blender数据结构作为键?显然,从某些存储库中,某些对象要求您首先“冻结”它们以避免变异(不允许对已用作python字典中键的基于值的对象进行变异)
6502年

1
@ kawing-chiu pythonfiddle.com/eq-method-needs-ne-method <-这显示了Python 2中的“错误” 。Python3 没有此问题:默认值__ne__()已被“修复”
Bob Stein

34

使用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

__hash__如果需要特殊的哈希语义,则可以覆盖,__cmp__或者__eq__为了使您的类可用作键。比较相等的对象需要具有相同的哈希值。

Python期望__hash__返回一个整数,Banana()不建议返回:)

如您所述,用户定义的类在__hash__默认情况下会调用id(self)

文档中还有一些其他技巧:

__hash__() 从父类继承方法但更改的含义__cmp__()__eq__() 使得返回的哈希值不再合适的类(例如,通过切换到基于值的相等性概念,而不是基于默认的基于身份的相等性),这些类可以显式地标记为可以通过__hash__ = None 在类定义中进行设置来取消哈希。这样做意味着,在程序尝试检索其哈希值时,该类的实例不仅会引发适当的TypeError,而且在检查时它们也将被正确标识为不可哈希 isinstance(obj, collections.Hashable) (与定义自己__hash__()明确地引发TypeError的类不同 )。


2
单靠哈希是不够的,此外,您还需要覆盖__eq____cmp__
Oben Sonne

@Oben Sonne:__cmp__如果它是用户定义的类,则由Python提供给您,但是您可能无论如何都要重写它们以适应新的语义。
Skurmedel 2011年

1
@Skurmedel:是的,但是尽管您可以在不覆盖这些方法的用户类上调用cmp和使用,但=必须实现其中之一,以满足发问者的要求,即名称和位置相似的实例具有相同的字典键。
Oben Sonne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.