Why does defining the argument types for __eq__ throw a MyPy type error?

Question:

I’m using Python 3.5.1 and the newly released MyPy v0.4.1 static type analyzer.

I have some more complex code that I’ve reduced down to this simplest possible python class needed to reproduce the error:

class MyObject(object):
    def __init__(self, value: int=5) -> None:
        self.value = value

    def __eq__(self, other: MyObject) -> bool:
        return self.value == other.value

Running the type checker mypy test.py produces the following error:

test.py: note: In class "MyObject":
test.py:5: error: Argument 1 of "__eq__" incompatible with supertype "object"

My theory based on these docs is that .__eq__(...) and .__ne__(...) on object already have argument types defined, which are clashing with my subclass’s redefinition of these types. My question is how to I define these types to make sure __eq__ is type-checked with my chosen type.

Asked By: Nick Sweeting

||

Answers:

== is supposed to take arbitrary other objects, not just objects of your type. If it doesn’t recognize the other object, it should return NotImplemented:

class MyObject(object):
    def __init__(self, value: int=5) -> None:
        self.value = value

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, MyObject):
            return NotImplemented
        return self.value == other.value

NotImplemented isn’t an instance of bool, but mypy seems to have a weird special case for that. It accepts this code as-is.

On Python 3.10 and up, you can use types.NotImplementedType to be more explicit about the NotImplemented possibility:

from types import NotImplementedType

class MyObject(object):
    def __init__(self, value: int=5) -> None:
        self.value = value

    def __eq__(self, other: object) -> bool | NotImplementedType:
        if not isinstance(other, MyObject):
            return NotImplemented
        return self.value == other.value

Also, if you need to refer to MyObject for type hints inside its own body, you need to use a string, 'MyObject' instead of MyObject. MyObject doesn’t exist yet.

Answered By: user2357112

Your reading of the docs is right — you need to give the method (__eq__) the same signature as it has already in the base class (object), or else a more permissive one.

The reason for that is that because your MyObject is a subtype of object, a MyObject could be passed anywhere that expects an object… which means that that code could compare it with any other object, and there’s no legitimate way for the type checker to complain. So, to reflect that, your __eq__ has to be written to expect any object.

What you can do is right up front in the method’s body, check the type and return (or raise an exception):

if not isinstance(other, MyObject):
  return False

Then as those docs say, Mypy is smart enough that after that check, it will know that other is a MyObject and treat it accordingly.

Answered By: Greg Price

The test using "isinstance()" only works if there’s no inheritance, if there’s is you either need to override eq in derived classes or use if type(self) != type(other)

Answered By: Rich Gooding