如何在Python中实现一个好的__hash__函数

136

当实现一个包含多个属性的类(就像下面的玩具示例中一样),处理哈希的最佳方式是什么?

我认为__eq____hash__应该保持一致,但如何实现一个适合处理所有属性的正确哈希函数呢?

class AClass:
  def __init__(self):
      self.a = None
      self.b = None

  def __eq__(self, other):
      return other and self.a == other.a and self.b == other.b

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

  def __hash__(self):
      return hash((self.a, self.b))

我在这个问题上读到元组是可哈希的,所以我想知道像上面示例一样的做法是否明智。它可行吗?


5
请确保对使用__eq__()和相关方法中进行比较的元素完全相同的元组使用hash()(就像您所做的那样),然后您就可以放心使用了。 - Feuermurmel
3个回答

102

__hash__需要为相等的对象返回相同的值。它还不应在对象的生命周期内更改;通常您只会为不可变对象实现它。

一个简单的实现是只返回0。这总是正确的,但性能不佳。

你的解决方案是返回一个属性元组的哈希值,这很好。但请注意,在元组中不需要列出您在__eq__中比较的所有属性。如果一些属性通常对于不相等的对象具有相同的值,请将其排除在外。不要使哈希计算比必要的更昂贵。

编辑:我建议一般不要使用xor来混合哈希值。当两个不同的属性具有相同的值时,它们将具有相同的哈希值,使用xor这些值将互相抵消。元组使用更复杂的计算来混合哈希值,请参见tupleobject.c中的tuplehash函数()。


5
正如你所说,哈希函数通常仅适用于不可变对象。因此,在__init__中计算哈希值是可行的。 - Björn Pollex
6
对于return 0哈希函数打+1 - 我一直认为其他的都是过早的优化 :-). (我只是半开玩笑)。 - Scott Griffiths
7
与其在 __init__ 中完成,您可以将值缓存到 __hash__ 中。这样,如果从未调用 __hash__,则既没有浪费时间也没有浪费内存。我假设检查该值是否已被缓存并不昂贵,是吗?(不确定最好使用异常还是显式的 if 来实现)。 - max
1
很遗憾,Python没有提供“combine_hashes”函数。 - Fred Foo
3
像字典或列表这样的数据结构中不支持该功能,原因是改变已经属于某个集合的对象的哈希值会对集合的内部数据结构造成破坏。 - javawizard
显示剩余6条评论

28

object.__hash__(self)的文档。

唯一必需的属性是比较相等的对象必须具有相同的哈希值;建议混合对象组成部分的哈希值,并通过将它们打包到元组中并对该元组进行哈希来进行对象比较。例如

def __hash__(self):
    return hash((self.name, self.nick, self.color))

5
可以工作,但如果交换self.aself.b,则会得到相同的哈希值,尽管它是另一个“对象”,这是不好的。 - eigenein
26
为什么不直接对元组的值进行哈希处理呢?hash((self.a, self.b)) - nightpool
8
请注意,幸运的是,在Python 3Python 2文档中不再建议使用xor。 - PM 2Ring
12
如果您感兴趣,这里是导致XOR推荐被删除的错误:https://bugs.python.org/issue28383 - AXO
1
@axo 我们应该使用元组的哈希值更新这个答案。 - fersarr
显示剩余3条评论

22

写入操作具有风险

def __eq__(self, other):
  return other and self.a == other.a and self.b == other.b

因为如果您的rhs(即other)对象被评估为布尔值False,它将永远不会被视为等于任何东西!

此外,您可能需要仔细检查other是否属于AClass的类或子类。如果不是,您将收到异常AttributeError或错误的正向结果(如果其他类恰好具有具有匹配值的同名属性)。因此,我建议将__eq__重写为:

def __eq__(self, other):
  return isinstance(other, self.__class__) and self.a == other.a and self.b == other.b

如果您碰巧需要一种非常灵活的比较方法,只要属性名称匹配,就可以在不相关的类之间进行比较,那么您仍然至少要避免AttributeError并检查other是否有任何额外的属性。 如何执行取决于情况(因为没有标准方法来查找对象的所有属性)。


8
有用的信息,但与关于哈希的主要问题无关。 - Mad Physicist
与编程无关,但还是感谢您发帖。+1。 - Leo Ufimtsev
1
这是一个糟糕的__eq__实现,因为如果左侧不知道如何进行比较,则不会委托给右侧的__eq__。如果int有这样的__eq__,即使MyNumericType(1) == 1返回True1 == MyNumericType(1)也总是会返回False。如果您不认识other的类型,请始终return NotImplemented,而不仅仅是return False - ShadowRanger
@ShadowRanger 同意。 - max

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