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.
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.
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.
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)
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.
==
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.
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.
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)