Reputation: 10638
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
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
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