【问题标题】:Is it unpythonic to have classes that are not hashable?拥有不可散列的类是不是很奇怪?
【发布时间】:2012-07-03 18:13:17
【问题描述】:

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

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

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

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

【问题讨论】:

  • 相等运算符似乎是必不可少的。实际上,我确实重载了相等运算符。但是,例如调用 'x==1' 不会返回布尔值,而是返回 表达式 'x==1'。
  • 这个东西说起来很麻烦,因为所讨论的类代表符号表达式。
  • 我希望 '==' 和类似的运算符与哈希一致,并在 python 绑定中使用其他运算符来组合两个子表达式和测试操作。
  • __eq__() 返回的表达式能否在布尔上下文中进行有意义的评估? if exp1 == exp2 有效吗?如果没有,定义__hash__() 是没用的。
  • 您是否有一个特定的用例来使表达式 '(x)''(1*x)' 比较相等,或者只是一个通用的想法,它会很有用?

标签: python hash


【解决方案1】:

只有当你有一个明确定义的相等性测试时,哈希函数才有用并且考虑到相等性测试的信息是不可变的。

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

根据您的问题,我无法完全确定这些条件是否成立。使类可散列不是“Pythonic”或“Unpythonic”——问题在于类的语义是否允许散列。

【讨论】:

  • 我是“同事”。我认为对于我们的应用程序,数据是不可变的,有意义的哈希是可能的并且(至少在某种程度上)是可取的。
  • 现在我们重写了 '==' 操作符,但哈希函数本质上仍然是 id()。
【解决方案2】:

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

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

但是,如果不给 __eq__ 一个更传统的定义,这种行为会很困难。正如文档所说,“Hashable objects which compare equal must have the same hash value”。所以如果__eq__5 + 58 + 2 相等,那么它们必须散列到相同的值。这意味着要使您的表达式像现在一样可散列,您必须选择一个__hash__,它能够为所有评估为相等的表达式确定规范形式。这对我来说听起来很难。

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

所以这一切都归结为您想以非常规的方式定义__eq__ 的程度。我想总的来说,我会使用__eq__ 的传统定义,只是为了避免产生意外行为。毕竟,special cases aren't special enough to break the rules

【讨论】:

  • 我选择了这个作为我的答案,因为我认为它最好地总结了所有方面,尽管许多其他答案都有非常相似的论点。谢谢!
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 他是说人类会认为xx*1 相等,但是如果没有大量额外的编码,这对于计算机来说是有问题的。
    • 两个表达式可能是等价的,例如总是计算相同的值,但并不总是检查这一点。例如,如果xy 是两个等价的表达式,x-y 将给出一个始终计算为 0 的表达式。相等的含义是创建一个计算结果为 0 的 表达式 (错误)或 1(正确)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 2013-06-10
    • 2016-04-17
    • 2023-02-09
    • 1970-01-01
    相关资源
    最近更新 更多