让__eq__引发异常的陷阱是什么?

4

我有一个类型,在这个类型中,等式比较没有意义。显式比较这个类型的两个实例的引用或值相等性将表明调用代码中存在逻辑错误。

定义__eq__引发异常是否不好?这方面有什么陷阱吗?它是否作为某些常见操作的一部分隐式调用?

在像Haskell这样的语言中,我将简单地不实现Equal类型类,并且尝试比较将是编译错误。由于Python是完全动态的,如果此定义不是一个好主意,我有哪些选项可以帮助鼓励正确使用?

我可以返回NotImplemented,但然后它会回退到比较,最终将导致身份比较,如果RHS也返回NotImplemented,而我仍然不想要那个。


2
不考虑你的代码,听起来你滥用了==运算符,应该使用其他运算符或函数代替。 - Dunes
2
Pythonic 的做法是记录用法,但如果用户坚持使用 ==,那就让他们使用吧。 - Dunes
3
我真诚地好奇,您创建了什么样的东西,使得引用相等性不合适。我理解为什么某些类型可能没有值比较性,但为什么引用相等性却有问题?如果调用代码执行a is b,会导致什么错误?(这不是批评,我想在这里学到一些东西?) - Ian McLaird
相信让 __eq__ 抛出异常会防止你的类被用作字典中的键。字典是一种常见的方式,可以将任意值“附加”到可比较引用对象上。 - user319799
简而言之,这些对象是更大项目的组成部分,只能在该更大项目的上下文中进行比较。 - Daenyth
显示剩余5条评论
1个回答

3
使__eq__抛出异常会防止您的类被用作字典中的键。字典是将任意值“附加”到可比较对象的常见方法。
例如:
class NotComparableAtAll:
    def __eq__(self, other):
        raise ValueError ('haha')

cache = { }
x, y = NotComparableAtAll (), NotComparableAtAll ()

cache[x] = 1
cache[y] = 2

这会失败,并显示该类型不可哈希。

然而,如果你将 __hash__ 添加到类中(默认在定义__eq__后被删除),这个例子看起来几乎总是正常工作的,只有在某些机器上两个不同的对象的哈希值相同时才可能出现不可预测的失败。要重现它,请将 __hash__ 定义为始终返回 0(这符合 __hash__ 的要求)。

此外,像这样覆盖 __eq__ 会破坏一些标准函数,其中按引用比较会很有用:

my_list = [x, y]
my_list.remove (y)

当然,这可以被视为“这些对象绝对不应该被比较”的另一个案例,但我想我们可以提出其他类似的例子,其中比较在某种有用的操作中嵌套。

抱歉,但我添加了 __hash__,它确实起作用了。将键值对添加到 cache 字典时没有出现任何异常或问题。(至少对于第一个键是这样的 :P) - KurzedMetal
@KurzedMetal:你是对的,看看编辑。从某种意义上说,情况甚至更糟:它似乎工作正常,只是在罕见的不可重现的情况下突然崩溃。 - user319799
这是个不好的想法的原因是:不可变对象应该总是按值进行比较,对于可变对象来说,没有理由打破引用相等性(为什么一个对象与自身不相等?) - KurzedMetal
这已经足够大的理由来避免考虑去做它。我将只能依赖文档。即使我的用例不会触发这种情况,但破坏字典哈希表意味着您也无法拥有这些集合。 - Daenyth

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