I have a subclass of list that adds some administrative stuff on the components of the list. Everything works OK, but mypy complains about signature of the super call to __setitem__.
Here is the problem reduced to its bare minimum:
from typing import List, Iterable, Union, overload
from typing_extensions import SupportsIndex
class MyData:
pass
class MyDataSeq(List[MyData]):
@overload
def __setitem__(self, index: SupportsIndex, value: MyData) -> None: ...
@overload
def __setitem__(self, index: slice, value: Iterable[MyData]) -> None: ...
def __setitem__(self, index: Union[SupportsIndex, slice], value: Union[MyData, Iterable[MyData]]) -> None:
# Administrative stuff deleted
super().__setitem__(index, value)
def __delitem__(self, index: Union[SupportsIndex, slice]) -> None:
# Administrative stuff deleted
super().__delitem__(index)
When I run mypy on this, I get:
src\seq.py:18: error: Invalid index type "Union[SupportsIndex, slice]" for "MyDataSeq"; expected type "SupportsIndex"
src\seq.py:18: error: Incompatible types in assignment (expression has type "Union[MyData, Iterable[MyData]]", target has type "MyData")
Found 2 errors in 1 file (checked 1 source file)
I'm at a loss here, because obviously __setitem__, just like __delitem__, accepts both an int-like (SupportsIndex) and a slice object as its first argument. It is almost as if mypy somehow reaches the conclusion that only int-like objects are supported -- that matches with the second error that it expects only a MyData as second argument, not Iterable[MyData].
I have tried this on both Python 3.7 and 3.9, the errors are the same.
I can of course tell mypy to ignore these errors, but I really would like to know what causes this, and how to solve it. Any ideas?
In accordance to this similar issue. Mypy doesn't use overload information to type-check the function body.
To solve your problem, you could give a hint to Mypy what types you are passing to it by means of isinstance. Like so:
from typing import List, Iterable, Union, overload
from typing_extensions import SupportsIndex
class MyData:
pass
class MyDataSeq(List[MyData]):
@overload
def __setitem__(self, index: SupportsIndex, value: MyData) -> None: ...
@overload
def __setitem__(self, index: slice, value: Iterable[MyData]) -> None: ...
def __setitem__(self, index: Union[SupportsIndex, slice], value: Union[MyData, Iterable[MyData]]) -> None:
# Administrative stuff deleted
if isinstance(index, slice) and isinstance(value, Iterable):
super().__setitem__(index, value)
elif isinstance(index, int) and isinstance(value, MyData):
super().__setitem__(index, value)
else:
raise TypeError(f"{index}/{value} Invalid index/value type.")
def __delitem__(self, index: Union[SupportsIndex, slice]) -> None:
# Administrative stuff deleted
super().__delitem__(index)
You don't need to annotate the implementation, only the decorated definitions.
class MyDataSeq(List[MyData]):
@overload
def __setitem__(self, index: SupportsIndex, value: MyData) -> None:
...
@overload
def __setitem__(self, index: slice, value: Iterable[MyData]) -> None:
...
def __setitem__(self, index, value):
# Administrative stuff deleted
super().__setitem__(index, value)
def __delitem__(self, index: Union[SupportsIndex, slice]) -> None:
# Administrative stuff deleted
super().__delitem__(index)
(This will leave a similar problem for __delitem__ to be resolved, but I think that's a matter for a separate question.)