InSync
InSync

Reputation: 10638

Using @singledispatchmethod on __eq__(): Signature of "__eq__" incompatible with supertype "object"

Here's a minimal reproducible example of what I'm trying to do (mypy playground):

from functools import singledispatchmethod


class C:
  
  value: int
  
  def __init__(self, value: int) -> None:
    self.value = value
  
  @singledispatchmethod
  def __eq__(self, other: object) -> bool:
    return NotImplemented
  
  @__eq__.register
  def _(self, other: C) -> bool:
    return self.value == other.value
    
  @__eq__.register
  def _(self, other: D) -> bool:
    return self.value == other.foo / 2

D is actually irrelevant to the problem, but here it is if you need it:

class D:
  
  value: int
  
  def __init__(self, value: int) -> None:
    self.value = value
  
  @property
  def foo(self) -> int:
    return 42 * self.value

mypy gives me the following error:

main.py:12: error: Signature of "__eq__" incompatible with supertype "object"  [override]
main.py:12: note:      Superclass:
main.py:12: note:          def __eq__(self, object, /) -> bool
main.py:12: note:      Subclass:
main.py:12: note:          singledispatchmethod[bool]

...which makes no sense at all. What can I do, other than using a type: ignore comment? I have read this question, but it is not about type hinting, and the only answer there leads to the same error (assuming I did not do anything incorrectly).

Upvotes: 0

Views: 263

Answers (2)

InSync
InSync

Reputation: 10638

This is how I wrote my __eq__() originally: Manual type checking.

class C:

  def __eq__(self, other: object) -> bool:
    if isinstance(other, C):
      return ...
    
    if isinstance(other, D):
      return ...

    return NotImplemented

This is mypy-friendly and magic-free, although it might not look nice in a class which has other @singledispatchmethod. This is not preferable to me, but it might be for someone else.

Upvotes: 0

dROOOze
dROOOze

Reputation: 3608

The type-checker is complaining because instances of functools.singledispatchmethod are not actually Callable (it has no __call__ method). You eventually get a callable through dot-attribute-access from the descriptor method __get__, but there is no attribute access here when you're simply defining the __eq__ method in the class body.

The "solution" is to lie to the type-checker, pretending that singledispatchmethod is actually Callable. The following is a minimal implementation to make it work with your example (see mypy-playground):

from __future__ import annotations

import typing as t

from functools import singledispatchmethod as _singledispatchmethod


if t.TYPE_CHECKING:
    import collections.abc as cx

    P = t.ParamSpec("P")
    T = t.TypeVar("T")

    class singledispatchmethod(_singledispatchmethod[T], t.Generic[P, T]):

        """
        Drop-in replacement for `functools.singledispatchmethod` when all the dispatched
        methods are of the same arity
        """

        def __init__(self, func: cx.Callable[t.Concatenate[t.Any, P], T], /) -> None: ...
        @t.type_check_only
        def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ...

else:
    singledispatchmethod: t.Final = _singledispatchmethod
>>> class C:
...     value: int
...
...     def __init__(self, value: int) -> None:
...         self.value = value
...
...     @singledispatchmethod
...     def __eq__(self, other: object, /) -> bool:  # OK
...         return NotImplemented

Upvotes: 1

Related Questions