Daenyth
Daenyth

Reputation: 37461

Pitfalls to having __eq__ raise an exception?

I have a type where equality comparison doesn't make sense. Explicitly comparing two instances of this type for either reference or value equality would indicate a logic error in the calling code.

Is it bad to define __eq__ to raise an exception? Are there pitfalls to this? Is it implicitly called as a part of some common operation?

In a language like Haskell I would simply not implement the Equal typeclass, and attempting the comparison would be a compile error. Since python is completely dynamic, do I have an option that would help encourage correct usage if this definition isn't a good idea?

I could return NotImplemented, but then it does fallback comparisons that will ultimately result in an identity comparison if the RHS also returns NotImplemented, and I still don't want that.

Upvotes: 5

Views: 796

Answers (1)

user319799
user319799

Reputation:

Making __eq__ throw will prevent your class from being used as keys in a dictionary. Dictionaries are a common way to "attach" arbitrary values to reference-comparable objects.

For example:

class NotComparableAtAll:
    def __eq__(self, other):
        raise ValueError ('haha')

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

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

This fails, saying that the type is not hashable.

However, if you do add __hash__ to the class (default is removed as soon as you define __eq__), the example appears to work fine almost all the time, only it can unpredictably fail if two distinct objects turn out to have the same hash on some machine. To reproduce it, define __hash__ to always return 0 (which does conform to __hash__ requirements).

Additionally, overriding __eq__ like that breaks some standard functions where comparison by reference would be useful:

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

This can of course be deemed another case of "these objects must really not be compared", but I guess one could come up with other similar examples where comparison is somehow nested inside a different useful operation.

Upvotes: 5

Related Questions