3

我正在和我的一位同事争论,是否所有的 Python 类都真的需要是可散列的。我们有这个包含符号表达式的类(类似于 SymPy)。

我的论点是,由于我们无法比较两个表达式的相等性,因此不应允许散列。例如,表达式 '(x)' 和 '(1*x)' 可能比较相等,而 'sqrt(x*x*x)' 和 'abs(x)*sqrt(x)' 可能不相等。因此,当使用符号表达式调用时,'hash()' 应该会引发错误。

他的论点是您应该能够将所有类用作字典和集合中的键。因此,它们也必须是可散列的。(我现在把话放在他嘴里,他会解释得更好。)。

谁是对的?如果您尝试对它们进行哈希处理,是否有抛出错误的类?

4

4 回答 4

4

仅当您具有明确定义的相等性测试并且考虑到相等性测试的信息是不可变的时,散列函数才有用。

默认情况下,所有用户定义的类都按对象标识进行比较,并且它们使用id()作为哈希值。如果您不覆盖==运算符,则很少有理由更改此行为。如果您确实override ==,并且此运算符中考虑的信息是不可变的(这意味着它在实例的提升时间内无法更改),您还可以定义一个散列函数以使实例可散列。

从你的问题来看,我不能完全判断这些条件是否成立。使类可散列不是“Pythonic”或“Unpythonic”——问题在于类的语义是否允许散列。

于 2012-07-03T18:18:18.590 回答
3

有很多内置的 Python 类型是不可散列的。因此,对于一个不可散列的类来说,这是完全 Pythonic 的。

您给出的示例是创建可散列类问题的一个很好的示例,因为要使对象可用作字典中的键,它必须同时实现__hash__()__eq__()。如果您不能可靠地确定相等性,那么哈希性无论如何都没有真正的好处,并且实施它是浪费精力。

于 2012-07-03T18:18:49.460 回答
3

我在这里看到的问题是您正在使用两种不同的平等概念。如果我正确理解了您的评论,则您已被覆盖__eq__以返回两个参数组合到==. 如果所述表达式的计算结果为 True(在某种意义上),那么这两个表达式相等;并且如果您的表达式类也以返回iff 表达式为真的方式实现__nonzero____bool__在 Python 3 中),那么从表面上看,这似乎应该可以正常工作。__nonzero__True

但事实上,在我看来,您定义的平等概念与 Python 中正常的平等概念相比,是一个非常不同的平等概念。哈希性的一个基本要求是,如果两个项目评估为相等,那么它们应该是完全可互换的。虽然您的两个表达式对象可能评估为“相等”,但我不确定它们是否可以互换!毕竟,5 + 5并且8 + 2评估到相同的结果,但它们并不相同,是吗?鉴于这两个表达式,我怀疑很多人会期望它们在字典中散列到两个单独的 bin 中!

然而,如果不给出__eq__更传统的定义,这种行为将是困难的。正如文档所说,“比较相等的可散列对象必须具有相同的散列值。” 因此,如果这么__eq__5 + 5并且8 + 2相等,那么它们必须散列到相同的值。这意味着要使您的表达式像现在一样可散列,您必须选择一个__hash__能够为所有计算为 equal 的表达式确定规范形式的表达式。这对我来说听起来很难。

简而言之,如果这些表达式是不可变的,并且如果您重新定义__eq__以返回Trueiff 表达式相同(比“等于”更强的要求),那么使它们可散列应该没有问题。另一方面,我认为不可散列的不可变类型没有任何问题。而且我不建议在不重新定义__eq__.

所以这一切都归结为你想以__eq__一种非常规的方式定义有多糟糕。我想总的来说,我会使用 的传统定义__eq__,只是为了避免产生意外行为。毕竟,特殊情况还不足以打破规则

于 2012-07-03T18:54:21.617 回答
2

拥有不可散列的类当然不是不合情理的,尽管你的理由不是我通常会给出的理由。一个类可能不可散列的主要原因是因为它是可变的,因此它的核心数据本身是不可散列的。例如,包装字典或列表的类就是这种情况。

我不太遵循您关于平等比较的逻辑。你说你不能比较表达式是否相等,但是你说某些表达式可能比较相等,也可能不相等。你能或不能比较它们的平等吗?如果你不能,那么说它们比较相等或不相等是没有意义的。

于 2012-07-03T18:18:14.433 回答