Python中针对相等值对象的__hash__方法

10

假设我有一些Person实体,我想知道其中一个是否在列表中:

person in people?

我不关心“对象ID”是什么,只关心它们的属性是否相同。因此我将此放入我的基类中:

# value comparison only
def __eq__(self, other):
    return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__)

def __ne__(self, other):
    return not self.__eq__(other)

但是为了能够在集合中测试相等性,我还需要定义哈希,所以……

# sets use __hash__ for equality comparison
def __hash__(self):
    return (
        self.PersonID,
        self.FirstName,
        self.LastName,
        self.etc_etc...
    ).__hash__()

问题是我不想列出每个属性,也不想在属性更改时每次修改哈希函数。

那么这样做可以吗?

# sets use __hash__ for equality comparison
def __hash__(self):
    values = tuple(self.__dict__.values())
    return hash(values)

这个方案是否明智,且性能损失不会太大?考虑到是一个 web 应用的情况。

非常感谢。


1
会不会出现ID相同但信息不同的人? - user2357112
如果两个人的ID都是“None”,我想比较他们的属性。 - Barry
3个回答

7
字典的无序性意味着,如果 dict 的顺序不同,tuple(self.__dict__.values()) 就容易产生不同的结果(例如,如果一个 dict 中属性的赋值顺序不同)。
因为你的 values 是可哈希的,所以你可以尝试这个方法:
return hash(frozenset(self.__dict__.items()))

另外,需要注意的是__hash__不需要考虑所有内容,因为当哈希值相等时,__eq__仍然会用于验证相等性。因此,你可能可以这样做:

return hash(self.PersonID)

假设PersonID在实例之间是相对唯一的。

我想做 person in people,其中没有人拥有PersonID。如果发生冲突,集合比较会回退到使用 __eq__ 吗? - Barry
集合比较在使用__hash__缩小搜索范围后,总是使用__eq__。这就是哈希集合(如Python中的set)的设计方式。哈希值从未被用作唯一键。 - nneonneo
3
例如,您可以让哈希函数在任何情况下返回4,即使这样做会影响使用您的对象作为键时集合和字典操作的性能,但不会影响程序的正确性。 - nneonneo
2
@keeny:忘了提一下:如果你确实经常期望PersonIDNone,那么你可以通过使用一个便宜的测试来改进唯一性,例如hash(self.PersonID or self.LastName)。这仍然只哈希一个东西,但在PersonID是假值(例如None)时选择LastName - nneonneo
1
@ahmed_khan_89:啊,这个答案已经十年了,是写给Python 2的。现在已经更新成Python 3了。 - nneonneo
显示剩余2条评论

2
如果您已经使用__eq____dict__相等性,则不使用__hash____dict__有点愚蠢。但是,values提供了一个任意排序的列表,其中包含关于哪个值对应于哪个属性的信息,因此该代码实际上无法工作。相反,您可以尝试使用以下方法:
return hash(tuple(sorted(self.__dict__.viewitems())))

或者

return hash(frozenset(self.__dict__.viewitems()))

两者都会消除排序问题并保留属性名称信息。


hash(sorted(...)) 不起作用,因为 sorted 返回一个不可哈希的 list - nneonneo
@nneonneo:该死,我记得曾经考虑过那个问题,但当我看其他东西时它就从我的脑海中溜走了。感谢你的纠正。 - user2357112

0

感谢您的好问题。您正在做我想做的事情。在阅读了这些答案之后,我做了类似的事情,但有一些不同之处。

def __str__(self):
    return "{}({})".format(type(self).__name__, ", ".join(["{}={}".format(k, self.__dict__[k]) for k in sorted(self.__dict__)]))
def __eq__(self, other):
    return isinstance(other, type(self)) and self.__dict__ == other.__dict__
def __ne__(self, other):
    return not self == other
def __hash__(self):
    return hash(tuple(self.__dict__[k] for k in sorted(self.__dict__)))

我为了额外的学分,包含了字符串方法,因为在思考哈希方法后,我重新做了这个。

我在另一个答案中发现,不应直接调用self.__eq__,所以我使用了==

这个哈希使用类属性值的元组,按键排序。这将确保元组中的排序是一致的。如果你排序了值,那么两个属性交换的情况将具有相同的哈希。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接