Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 have types already 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.

like image 613
Nick Sweeting Avatar asked May 31 '16 23:05

Nick Sweeting


People also ask

How do I make MYPY ignore error?

Silencing errors based on error codes You can use a special comment # type: ignore[code, ...] to only ignore errors with a specific error code (or codes) on a particular line. This can be used even if you have not configured mypy to show error codes.

What is MYPY in Python?

“Mypy is an optional static type checker for Python that aims to combine the benefits of dynamic (or 'duck') typing and static typing. Mypy combines the expressive power and convenience of Python with a powerful type system and compile-time type checking.” A little background on the Mypy project.

How do I ignore MYPY check?

Spurious errors and locally silencing the checker You can add a # type: ignore comment to tell mypy to ignore this error: import frobnicate # type: ignore frobnicate. start() # Okay! The second line is now fine, since the ignore comment causes the name frobnicate to get an implicit Any type.

What is the purpose of type hinting?

Type hints improve IDEs and linters. They make it much easier to statically reason about your code. Type hints help you build and maintain a cleaner architecture. The act of writing type hints forces you to think about the types in your program.


2 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 wants the return annotation to be bool, and it doesn't complain about the return NotImplemented line.

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.

like image 170
user2357112 supports Monica Avatar answered Oct 26 '22 21:10

user2357112 supports Monica


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.

like image 28
Greg Price Avatar answered Oct 26 '22 22:10

Greg Price