Closed giladsheffer closed 2 years ago
I've encountered this issue also.
In my case, changing my equivalent of B
provided a workaround. It looks like this:
class B(A):
@classmethod
def foo(cls) -> "B":
return super().foo()
This gets rid of the warnings I was getting, but I suspect it won't play nice with any further subclasses. I do want to be able to subclass B
further, so is there a better way of working around this without resorting to #type: ignore
?
I think this is still an issue in mypy v0.910
T = TypeVar("T", bound="ConfigFile") # pylint: disable=invalid-name
class ConfigFile:
def __init__(self, content: str) -> None:
self._content: str = content
@classmethod
def from_file(cls: Type[T], file_path: str) -> T:
with open(file=file_path, mode="r") as _file_handler:
return cls(content=_file_handler.read())
class FooConfigFile(ConfigFile):
@classmethod
def from_file(cls: Type[T], file_path="/tmp/foo") -> T:
return super().from_file(file_path=file_path)
results in
error: Argument 2 for "super" not an instance of argument 1
Confirming that this still happens in Mypy 0.930: https://mypy-play.net/?mypy=latest&python=3.10&gist=94cf4781e3ebc86b2d4ea9ef4cf7f2e7
0.942 still has this issue
@classmethod
def _extract(cls: Type[RegexExtractorType], match: Match) -> Dict[str, Any]:
kwargs = super()._extract(match)
error: Argument 2 for "super" not an instance of argument 1
A workaround is just to use a second TypeVar
for the subclass:
from typing import TypeVar, Type
T = TypeVar("T", bound="A")
class A:
@classmethod
def foo(cls: Type[T]) -> T:
print(cls.__qualname__)
return cls()
T2 = TypeVar("T2", bound="B")
class B(A):
@classmethod
def foo(cls: Type[T2]) -> T2:
return super().foo()
B.foo()
@AlexWaygood, that's cool, but it doesn't work for __new__
/metaclasses:
# test_case.py
from typing import Any, Mapping, Tuple, Type, TypeVar
_TT = TypeVar("_TT", bound=type)
class Foo(type):
def __new__(
mcls: Type[_TT],
name: str,
bases: Tuple[Type, ...],
namespace: Mapping[str, Any],
**kw: Any,
) -> _TT:
return super().__new__(mcls, name, bases, namespace, **kw) # <-- still errors
% mypy --version
mypy 0.941
% python --version
Python 3.9.10
% mypy --config=/dev/null test_case.py
/dev/null: No [mypy] section in config file
test_case.py:15: error: Argument 2 for "super" not an instance of argument 1
Found 1 error in 1 file (checked 1 source file)
@posita, there's two issues in your example:
The stub for type
in typeshed says that the namespace
argument in type.__new__
has to be dict[str, Any]
. But the third parameter of Foo.__new__
is annotated with Mapping[str, Any]
. Mapping[str, Any]
is not a subtype of dict[str, Any]
, so passing an object of type Mapping[str, Any]
to the third argument of super().__new__()
is unsafe.
Your _TT
TypeVar needs to be bound to a forward reference "Foo"
rather than bound to type
.
If I modify your snippet to the following:
from typing import Any, Dict, Mapping, Tuple, Type, TypeVar
_TT = TypeVar("_TT", bound="Foo")
class Foo(type):
def __new__(
mcls: Type[_TT],
name: str,
bases: Tuple[type, ...],
namespace: Dict[str, Any],
**kw: Any,
) -> _TT:
return super().__new__(mcls, name, bases, namespace, **kw)
Then mypy is happy with it 🙂
(I'm not saying this is ideal — it's just a workaround.)
The stub for typeshed is correct that the namespace
argument to type.__new__
has to be a dict
rather than any old Mapping
, FWIW:
>>> from collections import UserDict
>>> type.__new__(type, 'Foo', (), UserDict())
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: type.__new__() argument 3 must be dict, not UserDict
>>> type.__new__(type, 'Foo', (), {})
<class '__main__.Foo'>
Ah! Brilliant! My bad. Thanks for the schoolin'! 🙇
It's more of an inconvenience than a bug. Technically mypy is correct, Type[T]
bound to a higher class in the hierarchy is not a subclass of the current class. Defining a new type for every subclass though is quite annoying, so there should be some sort of a solution.
What is the actual behavior/output?
What are the versions of mypy and Python you are using? Python 3.8.5 mypy 0.790+dev.5e9682143720263466f0aaed1924ccb218160ee0 Do you see the same issue after installing mypy from Git master? Yes
What are the mypy flags you are using? --strict