Let's say I have a simple class intended for symbolical computations where == and != evaluate to another expression:
class Expr:
def __eq__(self, other: object) -> 'Expr': ...
def __ne__(self, other: object) -> 'Expr': ...
def __add__(self, other: 'Expr') -> 'Expr': ...
def __sub__(self, other: 'Expr') -> 'Expr': ...
This will result in following errors:
Return type of "__eq__" incompatible with supertype "object"
Return type of "__ne__" incompatible with supertype "object"
As far as I understand Python data model doesn't require rich comparison methods to return bool. Is it Mypy specific requirement or is there something wrong with my class definition?
This is because of the return type of object.__eq__ in typeshed. Formally you are right that any return type is accepted at runtime, but changing the static type could hide some "design errors" that are otherwise difficult to catch.
Thank you for a quick response @ilevkivskyi. How would you proceed in case like this? I thought about Any:
class Expr:
def __eq__(self, other: Any -> Any: ...
def __ne__(self, other: Any) -> Any: ...
which is a bit suboptimal but seems acceptable. Does it make sense to you?
Any is a possible solution. However, using #type: ignore comments on those two particular lines could be more strict, since mypy will catch errors below in code:
class Expr:
def __eq__(self, other: Any) -> 'Expr': # type: ignore
return Expr()
def __ne__(self, other: Any) -> 'Expr': # type: ignore
return 42 # Error: Incompatible return value type (got "int", expected "Expr")
x = Expr() == 0
x.bad # Error: "Expr" has no attribute "bad"
while with Any such error will be unnoticed by mypy.
EDIT: extended the example.
I wonder if the return type of object.__eq__ in typeshed should be object, since the bool return type is not quite correct. The same applies to __lt__ and friends.
Thanks @ilevkivskyi
@ilevkivskyi Using your suggestion of appending # type: ignore to the line does indeed work quite well. But I find it personally very counter-intuitive, seeing a line with type-hints and an additional type: ignore annotation. It makes it look like the type is completely ignored on that line.
I got this is issue by looking for: Argument 1 of "__eq__" incompatible with supertype "object"
from typing import Union
class Expr:
def __eq__(self, other):
# type: (Union[Expr, str]) -> bool
return False
In this case, I tried to put the '# type: ignore' in the same line as the comment type annotation without luck.
It needs to be on the line with the function declaration
@JukkaL commented on Feb 1, 2017
I wonder if the return type ofobject.__eq__in typeshed should beobject, since theboolreturn type is not quite correct. The same applies to__lt__and friends.
Given the implementation, I would suggest the return type should be Union[NoReturn, Any]. object.__eq__ always throws a NotImplementedError (hence the NoReturn) and Any allows sub-classes to override the return type to anything they like.
object.__eq__ can throw a NotImplementedError, it only returns bools (assuming it's not overridden in a subclass). I could be reading this wrong or looking in the wrong place though.NoReturn isn't necessary, as that's implied on every function (see discussions in other issues).Any, then x == y loses all type information, which isnt desirable for 99% of use cases, so existing signatures should remain bool.That said, I agree with you that we need to broaden the use cases that are supported. I like the unsafe override decorator proposal in #5704, and I think that that along with addressing some usability issues would go a long way.
Some related discussion in https://github.com/python/mypy/issues/6710, which is more about the unexpected downstream effects of the # type: ignore.
* Glancing at the CPython code, I don't think `object.__eq__` can throw a `NotImplementedError`, it only returns bools
I am so sorry, you are right! It does not throw NotImplementedError, instead it _returns NotImplemented_:
assert object().__eq__(object()) == NotImplemented
Which would be fine, because it is consistent with Any. But for the reasons mentioned above, the type annotation in typeshed is bool which makes it more restrictive than it needs to be.
Some related discussion in #6710, which is more about the unexpected downstream effects of the
# type: ignore.
Thanks a lot for linking to this! It is a difficult problem and I am happy to see you and the maintainers of mypy give it well-deserved attention.
Now it's possible to use # type: ignore[override], which is more explicit and safer than # type: ignore. Closing this issue on the assumption that this is good enough (better documentation would be useful, however).
Most helpful comment
Now it's possible to use
# type: ignore[override], which is more explicit and safer than# type: ignore. Closing this issue on the assumption that this is good enough (better documentation would be useful, however).