a Type is not recognized properly for subclassing
Bug Report
I'm explicitly typing a variable with Type (or type) and mypy thinks I should not subclass it.
To Reproduce
from typing import Type
class Base:
...
def get_base() -> Type[Base]:
return Base
BaseAlias: Type[Base] = get_base()
class Derived(BaseAlias):
...
Expected Behavior
There should be no error. I'm subclassing a type, which is ok. Furthermore, mypy has the information that it's a type so subclassing should not be questioned.
Actual Behavior
footypes.py:11: error: Variable "footypes.BaseAlias" is not valid as a type
footypes.py:11: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
footypes.py:11: error: Invalid base class "BaseAlias"
Found 2 errors in 1 file (checked 1 source file)
Same behavior if specifying BaseAlias: type = get_base() instead.
Your Environment
- Mypy version used: 0.910
- Mypy command-line flags: none
- Mypy configuration options from
mypy.ini(and other config files): none - Python version used: 3.9.0
- Operating system and version:
python:3.9docker image
Even this does not work:
class Base:
...
def get_base() -> Base:
...
BaseAlias = get_base()
class Derived(BaseAlias):
...
Outputs:
ex.py:11: error: Variable "ex.BaseAlias" is not valid as a type
ex.py:11: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
ex.py:11: error: Invalid base class "BaseAlias"
I'll see what we can do here.
I guess that the easiest way to explain this is with this example:
from typing import Type
class Base:
...
class NotSoBase(Base):
a = 1
def get_base() -> Type[Base]:
return NotSoBase
BaseAlias: Type[Base] = get_base()
class Derived(BaseAlias):
...
This example will raise the same error. But, it differs in what type we return from get_base(). Notice, that -> Type[Base] return annotation is still repsected, but we return NotSoBase type instead, which has extra fields.
In your example you expect Mypy to understand that we use Base as a supertype, but my counter-example shows that this is not trivial. We can end up with quite broken TypeInfos this way. 😞
@sobolevn Would you mind explaining you couterexample a bit more? As far as I understand, using BaseAlias as a superclass should basically only say "I'm defining something that implements the interface of Base", because the alias has type Type[Base]. Even if you have another superclass between them in the chain (NotSoBase in your example), the substitution principle shouldn't be violated.
I'm asking because I think I have another example which is related to this issue: https://mypy-play.net/?mypy=latest&python=3.10&gist=4f4e45da223f0991300d6a71bb14724d
@yrd I think that this:
def get_base() -> Type[Base]:
return NotSoBase
BaseAlias: Type[Base] = get_base()
class Some(BaseAlias): ...
Will generate inconsistent MRO for mypy. Imagine that this code is valid. Then, mypy would think that Some is a subtype of Base, but in reallity it would be a subtype of NotSoBase.
Hm, I see that now - thanks!
I've been hitting what I think is this same issue in bidict too. I've been silently 'type: ignoring' it, but figured I'd chime in here, in case this is another useful test case:
def namedbidict(
typename: str,
keyname: str,
valname: str,
*,
base_type: type[BidictBase[KT, VT]] = bidict,
) -> type[BidictBase[KT, VT]]:
...
class NamedBidict(base_type): # false positive errors here (see below)
...
$ mypy bidict
bidict/_named.py: note: In function "namedbidict":
bidict/_named.py:72:23: error: Variable "base_type" is not valid as a type
[valid-type]
class NamedBidict(base_type):
^
bidict/_named.py:72:23: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
bidict/_named.py:72:23: error: Invalid base class "base_type" [misc]
class NamedBidict(base_type):
^
Found 2 errors in 1 file (checked 26 source files)
You can model namedbidict as a class :)
If I understand you correctly, the namedbidict API has been in bidict since 2009, long before Python had type hints. Sharing this example in case it’s desirable for mypy to support such APIs. If not, fair enough!
This affects setuptool's get_unpatched method, which is typed as such:
def get_unpatched(item: _T) -> _T: ...
# Used like:
_Distribution = get_unpatched(distutils.core.Distribution)
It also lead to unexpected invalid-type errors for downstream users of libraries not aware of this problem. For instance, platformdirs.PlatformDirs is currently an invalid type: https://github.com/jaraco/jaraco.abode/blob/8843f360ee5d9bc1afb1bdb3119157addb4aaf06/jaraco/abode/config.py#L4C7-L4C19