Categories
mypy python python-3.x

mypy: “__eq__” incompatible with supertype “object”

this is my code:

class Person:
def __init__(self, id):
self.id = id
def __eq__(self, other: 'Person') -> bool:
return self.id == other.id
def compare(self, other: 'Person') -> bool:
return self.id == other.id

mypy throw error: Argument 1 of "__eq__" incompatible with supertype "object".

But if I remove __eq__ method, mypy won’t complain it though compare is same as __eq__, what should I do?

The root problem is that the __eq__ method is supposed to accept any object: doing my_object == 3 is legal at runtime, and should always return False. You can see this for yourself by checking the baseline type definition for object in Typeshed: the signature of __eq__ is given as def __eq__(self, o: object) -> bool: ...

So, in order to make this work, the correct way of implementing __eq__ would be to do the following:

def __eq__(self, other: object) -> bool:
if not isinstance(other, Person):
# If we return NotImplemented, Python will automatically try
# running other.__eq__(self), in case 'other' knows what to do with
# Person objects.
return NotImplemented
return self.id == other.id

And in fact, if you update the version of mypy you’re using, it’ll print out a note recommending you structure your code in this way.

However, the problem with this approach is that mypy will now no longer complain if you do something silly like Person() == 3. Technically, that ought to return a bool, but pragmatically, your code probably has a bug if you’re comparing a person object against an int.

Thankfully, mypy very recently acquired a feature that can flag these sorts of errors: --strict-equality. Now, when you run mypy with that flag, doing Person() == 3 will make mypy output errors like Non-overlapping equality check (left operand type: "Person", right operand type: "int") even if you define __eq__ in the way described above.

Note that you’ll need to use the latest version of mypy from master to use this flag until the next version of mypy (0.680) is released. That should happen in roughly 2 to 3 weeks as of time of writing.


If defining the __eq__ in the manner described above is not something you can do for whatever reason, I would personally recommend suppressing the type error instead of replacing Person with Any.

So basically, do this:

def __eq__(self, other: 'Person') -> bool:  # type: ignore
return self.id == other.id

…maybe along with an brief note of why you’re suppressing the error.

The rationale here is that this definition of __eq__ strictly speaking is unsafe (it violates something known as the Liskov substitution principle) — and if you need to do something unsafe, it’s probably better to explicitly mark that you’re subverting the type system rather then hiding it by using Any.

And at least this way, you can still make expressions like Person() == 3 be a type error — if you use Any, expressions like Person() == 3 will silently type-check. At that point, you might as well just use object and structure your code to behave correctly.